[clang] [CIR][X86]Implement generic fallback for x86 builtins (PR #177639)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 23 10:49:35 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Priyanshu Kumar (Priyanshu3820)
<details>
<summary>Changes</summary>
This PR implements generic fallback for the builtins that are not handled in `CIRGenBuiltinX86.cpp`. It also adds the infrastructure to implement similar fallback for other architectures that currently returns `std::nullopt`to make simple builtins for that architecture that map to LLVM intrinsics start working. It implements `rdpmc` builtin to test the implementation(support for `rdtsc/rdtscp` are also added).
---
Full diff: https://github.com/llvm/llvm-project/pull/177639.diff
6 Files Affected:
- (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+99-24)
- (modified) clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp (+20-4)
- (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+21)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+21)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+16)
- (added) clang/test/CIR/CodeGenBuiltins/X86/rd-builtins.c (+66)
``````````diff
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 0e5a5b531df78..5f2d47a06d8cb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -32,6 +32,17 @@ using namespace clang;
using namespace clang::CIRGen;
using namespace llvm;
+template <typename... Operands>
+static mlir::Value emitIntrinsicCallOp(CIRGenBuilderTy &builder,
+ mlir::Location loc, const StringRef str,
+ const mlir::Type &resTy,
+ Operands &&...op) {
+ return cir::LLVMIntrinsicCallOp::create(builder, loc,
+ builder.getStringAttr(str), resTy,
+ std::forward<Operands>(op)...)
+ .getResult();
+}
+
static RValue emitLibraryCall(CIRGenFunction &cgf, const FunctionDecl *fd,
const CallExpr *e, mlir::Operation *calleeValue) {
CIRGenCallee callee = CIRGenCallee::forDirect(calleeValue, GlobalDecl(fd));
@@ -349,6 +360,39 @@ static RValue emitBuiltinAlloca(CIRGenFunction &cgf, const CallExpr *e,
allocaAddr, builder.getVoidPtrTy(cgf.getCIRAllocaAddressSpace())));
}
+std::optional<mlir::Value> CIRGenFunction::emitGenericBuiltinIntrinsic(
+ unsigned builtinID, const CallExpr *expr, llvm::ArrayRef<mlir::Value> ops) {
+
+ mlir::Location loc = getLoc(expr->getExprLoc());
+
+ // Try to get an intrinsic name for the builtin
+ std::optional<std::string> maybeName = getIntrinsicNameForBuiltin(builtinID);
+ if (!maybeName)
+ return std::nullopt;
+ llvm::StringRef intrinsicName = *maybeName;
+
+ CIRGenBuilderTy &builder = getBuilder();
+
+ // Determine return type based on AST
+ QualType retAstTy = expr->getType();
+ mlir::Type retTy;
+ if (retAstTy->isIntegerType()) {
+ unsigned width = (unsigned)getContext().getTypeSize(retAstTy);
+ retTy = retAstTy->isSignedIntegerOrEnumerationType()
+ ? builder.getSIntNTy(width)
+ : builder.getUIntNTy(width);
+ } else if (retAstTy->isVoidType())
+ retTy = builder.getVoidTy();
+ else
+ // Not supported by the simple fallback.
+ return std::nullopt;
+
+ SmallVector<mlir::Value> callArgs(ops.begin(), ops.end());
+ coerceCallArgsToASTTypes(callArgs, expr);
+
+ return emitIntrinsicCallOp(builder, loc, intrinsicName, retTy, callArgs);
+}
+
static bool shouldCIREmitFPMathIntrinsic(CIRGenFunction &cgf, const CallExpr *e,
unsigned builtinID) {
std::optional<bool> errnoOverriden;
@@ -1810,30 +1854,40 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
// Now see if we can emit a target-specific builtin.
// FIXME: This is a temporary mechanism (double-optional semantics) that will
- // go away once everything is implemented:
- // 1. return `mlir::Value{}` for cases where we have issued the diagnostic.
- // 2. return `std::nullopt` in cases where we didn't issue a diagnostic
- // but also didn't handle the builtin.
- if (std::optional<mlir::Value> rst =
- emitTargetBuiltinExpr(builtinID, e, returnValue)) {
- mlir::Value v = rst.value();
- // CIR dialect operations may have no results, no values will be returned
- // even if it executes successfully.
- if (!v)
- return RValue::get(nullptr);
-
- switch (evalKind) {
- case cir::TEK_Scalar:
- if (mlir::isa<cir::VoidType>(v.getType()))
+ // go away once everything is implemented.
+ // Return semantics:
+ // 1. `std::nullopt`: the target did NOT handle the builtin (caller should
+ // fall back or emit an error).
+ // 2. Engaged optional with a null `mlir::Value` (i.e., `mlir::Value{}`): the
+ // target handled the builtin but produced no result (void) or issued a
+ // diagnostic.
+ // 3. Engaged optional with a non-null `mlir::Value`: the target handled the
+ // builtin and returned a value.
+ {
+ std::optional<mlir::Value> rst =
+ emitTargetBuiltinExpr(builtinID, e, returnValue);
+ if (rst) {
+ mlir::Value v = rst.value();
+
+ // CIR dialect operations may have no results, no values will be returned
+ // even if it executes successfully.
+ if (!v)
return RValue::get(nullptr);
- return RValue::get(v);
- case cir::TEK_Aggregate:
- cgm.errorNYI(e->getSourceRange(), "aggregate return value from builtin");
- return getUndefRValue(e->getType());
- case cir::TEK_Complex:
- llvm_unreachable("No current target builtin returns complex");
+
+ switch (evalKind) {
+ case cir::TEK_Scalar:
+ if (mlir::isa<cir::VoidType>(v.getType()))
+ return RValue::get(nullptr);
+ return RValue::get(v);
+ case cir::TEK_Aggregate:
+ cgm.errorNYI(e->getSourceRange(),
+ "aggregate return value from builtin");
+ return getUndefRValue(e->getType());
+ case cir::TEK_Complex:
+ llvm_unreachable("No current target builtin returns complex");
+ }
+ llvm_unreachable("Bad evaluation kind in EmitBuiltinExpr");
}
- llvm_unreachable("Bad evaluation kind in EmitBuiltinExpr");
}
cgm.errorNYI(e->getSourceRange(),
@@ -1875,8 +1929,29 @@ emitTargetArchBuiltinExpr(CIRGenFunction *cgf, unsigned builtinID,
return std::nullopt;
case llvm::Triple::x86:
- case llvm::Triple::x86_64:
- return cgf->emitX86BuiltinExpr(builtinID, e);
+ case llvm::Triple::x86_64: {
+ // Try the target-specific emitter first. If it doesn't handle the
+ // builtin, fall back to the generic builtin->intrinsic emitter so target
+ // files don't have to call it themselves.
+ if (std::optional<mlir::Value> res = cgf->emitX86BuiltinExpr(builtinID, e))
+ return *res;
+
+ // Build the operand list
+ llvm::SmallVector<mlir::Value> ops;
+ unsigned iceArguments = 0;
+ ASTContext::GetBuiltinTypeError error;
+ cgf->getContext().GetBuiltinType(builtinID, error, &iceArguments);
+ assert(error == ASTContext::GE_None && "Error while getting builtin type.");
+
+ for (auto [idx, arg] : llvm::enumerate(e->arguments()))
+ ops.push_back(cgf->emitScalarOrConstFoldImmArg(iceArguments, idx, arg));
+
+ if (std::optional<mlir::Value> maybeRes =
+ cgf->emitGenericBuiltinIntrinsic(builtinID, e, ops))
+ return *maybeRes;
+
+ return std::nullopt;
+ }
case llvm::Triple::ppc:
case llvm::Triple::ppcle:
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
index d517085b34625..f411ab6589cf4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
@@ -798,11 +798,27 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
case X86::BI_m_prefetchw:
return emitPrefetch(*this, builtinID, expr, ops);
case X86::BI__rdtsc:
+ return emitIntrinsicCallOp(builder, getLoc(expr->getExprLoc()), "x86.rdtsc",
+ builder.getUInt64Ty());
case X86::BI__builtin_ia32_rdtscp: {
- cgm.errorNYI(expr->getSourceRange(),
- std::string("unimplemented X86 builtin call: ") +
- getContext().BuiltinInfo.getName(builtinID));
- return mlir::Value{};
+ mlir::Location loc = getLoc(expr->getExprLoc());
+
+ // Record type { i64, i32 }
+ mlir::Type recordTy = cir::RecordType::get(
+ &getMLIRContext(), {builder.getUInt64Ty(), builder.getUInt32Ty()},
+ /*packed=*/false, /*padded*/ false,
+ cir::RecordType::RecordKind::Struct);
+ mlir::Value call =
+ emitIntrinsicCallOp(builder, loc, "x86.rdtscp", recordTy);
+
+ // Aux (i32) -> store to pointer arg ops[0]
+ mlir::Value aux = cir::ExtractMemberOp::create(
+ builder, loc, builder.getUInt32Ty(), call, 1);
+ builder.CIRBaseBuilderTy::createStore(loc, aux, ops[0]);
+
+ // Return timestamp (i64)
+ return cir::ExtractMemberOp::create(builder, loc, builder.getUInt64Ty(),
+ call, 0);
}
case X86::BI__builtin_ia32_lzcnt_u16:
case X86::BI__builtin_ia32_lzcnt_u32:
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 5fd11c6d97c07..1c2b9a969d239 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -883,3 +883,24 @@ void CIRGenFunction::emitCallArgs(
std::reverse(args.begin() + callArgsStart, args.end());
}
}
+
+void CIRGenFunction::coerceCallArgsToASTTypes(
+ llvm::SmallVectorImpl<mlir::Value> &callArgs,
+ const clang::CallExpr *callExpr) {
+ CIRGenBuilderTy &builder = getBuilder();
+ for (unsigned i = 0; i < callArgs.size(); ++i) {
+ if (i >= callExpr->getNumArgs())
+ break;
+ QualType argTy = callExpr->getArg(i)->getType();
+ if (argTy->isIntegerType()) {
+ unsigned w = (unsigned)getContext().getTypeSize(argTy);
+ mlir::Type wantTy = argTy->isSignedIntegerOrEnumerationType()
+ ? builder.getSIntNTy(w)
+ : builder.getUIntNTy(w);
+ if (callArgs[i].getType() != wantTy)
+ callArgs[i] = builder.createBitcast(callArgs[i], wantTy);
+ }
+ // Pointer addrspace and other coercions are intentionally left to
+ // other lowering phases or specialized handlers.
+ }
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index ac66d00950f05..d4d29b783ca28 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -33,6 +33,27 @@ CIRGenFunction::CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
CIRGenFunction::~CIRGenFunction() {}
+std::optional<std::string>
+CIRGenFunction::getIntrinsicNameForBuiltin(unsigned builtinID) {
+ // First try explicit mappings for known builtins.
+ switch (builtinID) {
+ case X86::BI__builtin_ia32_rdpmc:
+ return std::string("x86.rdpmc");
+ default:
+ break;
+ }
+
+ // As a fallback, match common builtin names to avoid depending on the exact
+ // enum variant emitted by TableGen (e.g., "rdpmc" vs "__builtin_ia32_rdpmc").
+ std::string name = getContext().BuiltinInfo.getName(builtinID);
+
+ if (name.size() >= 5 && name.compare(name.size() - 5, 5, "rdpmc") == 0) {
+ return std::string("x86.rdpmc");
+ }
+
+ return std::nullopt;
+}
+
// This is copied from clang/lib/CodeGen/CodeGenFunction.cpp
cir::TypeEvaluationKind CIRGenFunction::getEvaluationKind(QualType type) {
type = type.getCanonicalType();
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index adcf4d56e3892..c43d0e8353995 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1457,6 +1457,12 @@ class CIRGenFunction : public CIRGenTypeCache {
CallArgList &args, PrototypeWrapper prototype,
llvm::iterator_range<clang::CallExpr::const_arg_iterator> argRange,
AbstractCallee callee = AbstractCallee(), unsigned paramsToSkip = 0);
+
+ /// Coerce a vector of emitted call operands so they match the AST argument
+ /// types (width and signedness) where possible. This mirrors the integer
+ /// coercion subset that Classic CodeGen performs for call arguments.
+ void coerceCallArgsToASTTypes(llvm::SmallVectorImpl<mlir::Value> &callArgs,
+ const clang::CallExpr *callExpr);
RValue emitCallExpr(const clang::CallExpr *e,
ReturnValueSlot returnValue = ReturnValueSlot());
LValue emitCallExprLValue(const clang::CallExpr *e);
@@ -1869,6 +1875,16 @@ class CIRGenFunction : public CIRGenTypeCache {
emitTargetBuiltinExpr(unsigned builtinID, const clang::CallExpr *e,
ReturnValueSlot &returnValue);
+ // Only include builtins that lack special handling
+ std::optional<std::string> getIntrinsicNameForBuiltin(unsigned builtinID);
+
+ /// Emit a generic builtin as an LLVM intrinsic call if a mapping exists.
+ /// Returns std::nullopt if the builtin is not handled by the generic
+ /// intrinsic fallback.
+ std::optional<mlir::Value>
+ emitGenericBuiltinIntrinsic(unsigned builtinID, const clang::CallExpr *expr,
+ llvm::ArrayRef<mlir::Value> ops);
+
/// Given a value and its clang type, returns the value casted to its memory
/// representation.
/// Note: CIR defers most of the special casting to the final lowering passes
diff --git a/clang/test/CIR/CodeGenBuiltins/X86/rd-builtins.c b/clang/test/CIR/CodeGenBuiltins/X86/rd-builtins.c
new file mode 100644
index 0000000000000..07b366440c544
--- /dev/null
+++ b/clang/test/CIR/CodeGenBuiltins/X86/rd-builtins.c
@@ -0,0 +1,66 @@
+// RUN: %clang -target x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang -target x86_64-unknown-linux-gnu -fclangir -S -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang -target x86_64-unknown-linux-gnu -S -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+#include <x86intrin.h>
+
+unsigned long long test_rdpmc(int a) {
+// CIR-LABEL: test_rdpmc
+// CIR: %{{.*}} = cir.call @__rdpmc(%{{.*}}) : (!s32i) -> !u64i
+// CIR: cir.store %{{.*}} : !u64i, !cir.ptr<!u64i>
+// CIR: cir.return %{{.*}} : !u64i
+
+// LLVM-LABEL: test_rdpmc
+// LLVM: %{{.*}} = call i64 @llvm.x86.rdpmc(i32 %{{.*}})
+// LLVM: store i64 %{{.*}}, ptr %{{.*}}
+// LLVM: ret i64 %{{.*}}
+
+// OGCG-LABEL: test_rdpmc
+// OGCG: %{{.*}} = call i64 @llvm.x86.rdpmc(i32 %{{.*}})
+// OGCG: ret i64 %{{.*}}
+ return _rdpmc(a);
+}
+
+int test_rdtsc(void) {
+// CIR-LABEL: test_rdtsc
+// CIR: cir.call_llvm_intrinsic "x86.rdtsc"
+// CIR: cir.cast integral %{{.*}} : !u64i -> !s32i
+// CIR: cir.return %{{.*}} : !u64i
+
+// LLVM-LABEL: test_rdtsc
+// LLVM: %{{.*}} = call i64 @llvm.x86.rdtsc()
+// LLVM: %{{.*}} = trunc i64 %{{.*}} to i32
+// LLVM: ret i32 %{{.*}}
+
+// OGCG-LABEL: test_rdtsc
+// OGCG: %{{.*}} = call i64 @llvm.x86.rdtsc()
+// OGCG: %{{.*}} = trunc i64 %{{.*}} to i32
+// OGCG: ret i32 %{{.*}}
+
+ return _rdtsc();
+}
+
+unsigned long long test_rdtscp(unsigned int *a) {
+// CIR-LABEL: test_rdtscp
+// CIR: %{{.*}} = cir.call @__rdtscp(%{{.*}}) : (!cir.ptr<!u32i>) -> !u64i
+// CIR: cir.store %{{.*}} : !u64i, !cir.ptr<!u64i>
+// CIR: cir.return %{{.*}} : !u64i
+
+// LLVM-LABEL: test_rdtscp
+// LLVM: %{{.*}} = call { i64, i32 } @llvm.x86.rdtscp()
+// LLVM: %{{.*}} = extractvalue { i64, i32 } %{{.*}}, 1
+// LLVM: store i32 %{{.*}}, ptr %{{.*}}
+// LLVM: %{{.*}} = extractvalue { i64, i32 } %{{.*}}, 0
+// LLVM: ret i64 %{{.*}}
+
+// OGCG-LABEL: test_rdtscp
+// OGCG: %{{.*}} = call { i64, i32 } @llvm.x86.rdtscp()
+// OGCG: %{{.*}} = extractvalue { i64, i32 } %{{.*}}, 1
+// OGCG: store i32 %{{.*}}, ptr %{{.*}}
+// OGCG: %{{.*}} = extractvalue { i64, i32 } %{{.*}}, 0
+// OGCG: ret i64 %{{.*}}
+ return __rdtscp(a);
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/177639
More information about the cfe-commits
mailing list