Skip to content

[CIR] Add Support For Library Builtins #143984

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 4 commits into from
Jun 16, 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
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ struct MissingFeatures {
static bool runCleanupsScope() { return false; }
static bool lowerAggregateLoadStore() { return false; }
static bool dataLayoutTypeAllocSize() { return false; }
static bool asmLabelAttr() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
39 changes: 37 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@
#include "mlir/Support/LLVM.h"
#include "clang/AST/Expr.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/Support/ErrorHandling.h"

using namespace clang;
using namespace clang::CIRGen;
using namespace llvm;

static RValue emitLibraryCall(CIRGenFunction &cgf, const FunctionDecl *fd,
const CallExpr *e, mlir::Operation *calleeValue) {
CIRGenCallee callee = CIRGenCallee::forDirect(calleeValue, GlobalDecl(fd));
return cgf.emitCall(e->getCallee()->getType(), callee, e, ReturnValueSlot());
}

RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
const CallExpr *e,
Expand All @@ -49,7 +57,34 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
}
}

mlir::Location loc = getLoc(e->getExprLoc());
cgm.errorNYI(loc, "non constant foldable builtin calls");
const FunctionDecl *fd = gd.getDecl()->getAsFunction();

// If this is an alias for a lib function (e.g. __builtin_sin), emit
// the call using the normal call path, but using the unmangled
// version of the function name.
if (getContext().BuiltinInfo.isLibFunction(builtinID))
return emitLibraryCall(*this, fd, e,
cgm.getBuiltinLibFunction(fd, builtinID));

cgm.errorNYI(e->getSourceRange(), "unimplemented builtin call");
return getUndefRValue(e->getType());
}

/// Given a builtin id for a function like "__builtin_fabsf", return a Function*
/// for "fabsf".
cir::FuncOp CIRGenModule::getBuiltinLibFunction(const FunctionDecl *fd,
unsigned builtinID) {
assert(astContext.BuiltinInfo.isLibFunction(builtinID));

// Get the name, skip over the __builtin_ prefix (if necessary). We may have
// to build this up so provide a small stack buffer to handle the vast
// majority of names.
llvm::SmallString<64> name;

assert(!cir::MissingFeatures::asmLabelAttr());
name = astContext.BuiltinInfo.getName(builtinID).substr(10);

GlobalDecl d(fd);
mlir::Type type = convertType(fd->getType());
return getOrCreateCIRFunction(name, type, d, /*forVTable=*/false);
}
4 changes: 4 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ class CIRGenModule : public CIRGenTypeCache {
cir::FuncType funcType,
const clang::FunctionDecl *funcDecl);

/// Given a builtin id for a function like "__builtin_fabsf", return a
/// Function* for "fabsf".
cir::FuncOp getBuiltinLibFunction(const FunctionDecl *fd, unsigned builtinID);

mlir::IntegerAttr getSize(CharUnits size) {
return builder.getSizeFromCharUnits(size);
}
Expand Down
18 changes: 18 additions & 0 deletions clang/test/CIR/CodeGen/builtin_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,21 @@ float constant_fp_builtin_single() {
// OGCG: define {{.*}}float @_Z26constant_fp_builtin_singlev()
// OGCG: ret float 0x3FB99999A0000000
// OGCG: }

void library_builtins() {
__builtin_printf(nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Will __builtin_printf also work with format and data arguments? Can you add a test case for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added builtin_printf.cpp that contains a few printf calls

__builtin_abort();
}

// CIR: cir.func @_Z16library_builtinsv() {
// CIR: %[[NULL:.+]] = cir.const #cir.ptr<null> : !cir.ptr<!s8i>
// CIR: cir.call @printf(%[[NULL]]) : (!cir.ptr<!s8i>) -> !s32i
// CIR: cir.call @abort() : () -> ()

// LLVM: define void @_Z16library_builtinsv()
// LLVM: call i32 (ptr, ...) @printf(ptr null)
// LLVM: call void @abort()

// OGCG: define dso_local void @_Z16library_builtinsv()
// OGCG: call i32 (ptr, ...) @printf(ptr noundef null)
// OGCG: call void @abort()
65 changes: 65 additions & 0 deletions clang/test/CIR/CodeGen/builtin_printf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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

// CIR: cir.global "private" cir_private dsolocal @".str" = #cir.const_array<"%s\00" : !cir.array<!s8i x 3>> : !cir.array<!s8i x 3>
// CIR: cir.global "private" cir_private dsolocal @".str.1" = #cir.const_array<"%s %d\0A\00" : !cir.array<!s8i x 7>> : !cir.array<!s8i x 7>
// LLVM: @.str = private global [3 x i8] c"%s\00"
// LLVM: @.str.1 = private global [7 x i8] c"%s %d\0A\00"
// OGCG: @.str = private unnamed_addr constant [3 x i8] c"%s\00"
// OGCG: @.str.1 = private unnamed_addr constant [7 x i8] c"%s %d\0A\00"

void func(char const * const str, int i) {
__builtin_printf(nullptr);
__builtin_printf("%s", str);
__builtin_printf("%s %d\n", str, i);
}

// CIR: cir.func @printf(!cir.ptr<!s8i>, ...) -> !s32i

// CIR: cir.func @_Z4funcPKci(%[[arg0:.+]]: !cir.ptr<!s8i>{{.*}}, %[[arg1:.+]]: !s32i{{.*}}) {
// CIR: %[[str_ptr:.+]] = cir.alloca !cir.ptr<!s8i>, !cir.ptr<!cir.ptr<!s8i>>, ["str", init, const]
// CIR: %[[i_ptr:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["i", init]
// CIR: cir.store %[[arg0]], %[[str_ptr]] : !cir.ptr<!s8i>, !cir.ptr<!cir.ptr<!s8i>>
// CIR: cir.store %[[arg1]], %[[i_ptr]] : !s32i, !cir.ptr<!s32i>
// CIR: %[[null_ptr:.+]] = cir.const #cir.ptr<null> : !cir.ptr<!s8i>
// CIR: %[[printf_result1:.+]] = cir.call @printf(%[[null_ptr]]) : (!cir.ptr<!s8i>) -> !s32i
// CIR: %[[str_fmt_global:.+]] = cir.get_global @".str" : !cir.ptr<!cir.array<!s8i x 3>>
// CIR: %[[str_fmt_ptr:.+]] = cir.cast(array_to_ptrdecay, %[[str_fmt_global]] : !cir.ptr<!cir.array<!s8i x 3>>), !cir.ptr<!s8i>
// CIR: %[[str_val:.+]] = cir.load{{.*}} %[[str_ptr]] : !cir.ptr<!cir.ptr<!s8i>>, !cir.ptr<!s8i>
// CIR: %[[printf_result2:.+]] = cir.call @printf(%[[str_fmt_ptr]], %[[str_val]]) : (!cir.ptr<!s8i>, !cir.ptr<!s8i>) -> !s32i
Copy link
Member

Choose a reason for hiding this comment

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

nit: please also check for printf declaration to be available. (I would make this test smaller by only checking the actual cir.call printfs and using {{.*}} for the params, but I'm fine with it as is too).

// CIR: %[[full_fmt_global:.+]] = cir.get_global @".str.1" : !cir.ptr<!cir.array<!s8i x 7>>
// CIR: %[[full_fmt_ptr:.+]] = cir.cast(array_to_ptrdecay, %[[full_fmt_global]] : !cir.ptr<!cir.array<!s8i x 7>>), !cir.ptr<!s8i>
// CIR: %[[str_val2:.+]] = cir.load{{.*}} %[[str_ptr]] : !cir.ptr<!cir.ptr<!s8i>>, !cir.ptr<!s8i>
// CIR: %[[i_val:.+]] = cir.load{{.*}} %[[i_ptr]] : !cir.ptr<!s32i>, !s32i
// CIR: %[[printf_result3:.+]] = cir.call @printf(%[[full_fmt_ptr]], %[[str_val2]], %[[i_val]]) : (!cir.ptr<!s8i>, !cir.ptr<!s8i>, !s32i) -> !s32i
// CIR: cir.return

// LLVM: define void @_Z4funcPKci(ptr %[[arg0:.+]], i32 %[[arg1:.+]])
// LLVM: %[[str_ptr:.+]] = alloca ptr
// LLVM: %[[i_ptr:.+]] = alloca i32
// LLVM: store ptr %[[arg0]], ptr %[[str_ptr]]{{.*}}
// LLVM: store i32 %[[arg1]], ptr %[[i_ptr]]{{.*}}
// LLVM: %[[printf_result1:.+]] = call i32 (ptr, ...) @printf(ptr null)
// LLVM: %[[str_val:.+]] = load ptr, ptr %[[str_ptr]]{{.*}}
// LLVM: %[[printf_result2:.+]] = call i32 (ptr, ...) @printf(ptr @.str, ptr %[[str_val]])
// LLVM: %[[str_val2:.+]] = load ptr, ptr %[[str_ptr]]{{.*}}
// LLVM: %[[i_val:.+]] = load i32, ptr %[[i_ptr]]{{.*}}
// LLVM: %[[printf_result3:.+]] = call i32 (ptr, ...) @printf(ptr @.str.1, ptr %[[str_val2]], i32 %[[i_val]])
// LLVM: ret void

// OGCG: define dso_local void @_Z4funcPKci(ptr noundef %[[arg0:.+]], i32 noundef %[[arg1:.+]])
// OGCG: %[[str_ptr:.+]] = alloca ptr
// OGCG: %[[i_ptr:.+]] = alloca i32
// OGCG: store ptr %[[arg0]], ptr %[[str_ptr]]{{.*}}
// OGCG: store i32 %[[arg1]], ptr %[[i_ptr]]{{.*}}
// OGCG: %[[printf_result1:.+]] = call i32 (ptr, ...) @printf(ptr noundef null)
// OGCG: %[[str_val:.+]] = load ptr, ptr %[[str_ptr]]{{.*}}
// OGCG: %[[printf_result2:.+]] = call i32 (ptr, ...) @printf(ptr noundef @.str, ptr noundef %[[str_val]])
// OGCG: %[[str_val2:.+]] = load ptr, ptr %[[str_ptr]]{{.*}}
// OGCG: %[[i_val:.+]] = load i32, ptr %[[i_ptr]]{{.*}}
// OGCG: %[[printf_result3:.+]] = call i32 (ptr, ...) @printf(ptr noundef @.str.1, ptr noundef %[[str_val2]], i32 noundef %[[i_val]])
// OGCG: ret void
Loading