Skip to content

Conversation

@rturrado
Copy link
Contributor

Adds support for the __builtin_ia32_cpuid and __builtin_ia32_cpuidex X86 builtins.

Part of 167765.

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Dec 21, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 21, 2025

@llvm/pr-subscribers-clang

Author: Roberto Turrado Camblor (rturrado)

Changes

Adds support for the __builtin_ia32_cpuid and __builtin_ia32_cpuidex X86 builtins.

Part of 167765.


Full diff: https://github.com/llvm/llvm-project/pull/173197.diff

3 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+27)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp (+10-1)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+70)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 7e7424fd71878..c1292ac6ce241 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -5762,4 +5762,31 @@ def CIR_BlockAddressOp : CIR_Op<"block_address", [Pure]> { }]; } +//===----------------------------------------------------------------------===// +// CpuIdOp +//===----------------------------------------------------------------------===// + +def CIR_CpuIdOp : CIR_Op<"cpuid"> { + let summary = "Get information about the CPU"; + let description = [{ + The `cir.cpuid` operation takes a base pointer to an array of 4 integers, a + function ID and a sub-function ID. The array of 4 integers is filled with + different information about the processor. + + Example: + + ```mlir + cir.cpuid %basePtr, %funcId, %subFuncId + : (!cir.ptr<!cir.array<4 x !s32i>>, !s32i, !s32i) -> + ``` + }]; + + let arguments = + (ins Arg<CIR_PtrToArray, "array address", [MemWrite]>:$basePtr, + CIR_SInt32:$funcId, CIR_SInt32:$subFuncId); + // TODO: remove once we can return an optional mlir::Value from + // emitX86BuiltinExpr + let results = (outs CIR_VectorType:$result); +} + #endif // CLANG_CIR_DIALECT_IR_CIROPS_TD diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp index 1c87e945de846..c58391ecc5b42 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp @@ -25,6 +25,7 @@ #include "clang/CIR/Dialect/IR/CIRTypes.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/ADT/Sequence.h" +#include "llvm/IR/InlineAsm.h" #include "llvm/Support/ErrorHandling.h" #include <string> @@ -1835,7 +1836,15 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) { case X86::BI__builtin_ia32_cvtneps2bf16_256_mask: case X86::BI__builtin_ia32_cvtneps2bf16_512_mask: case X86::BI__cpuid: - case X86::BI__cpuidex: + case X86::BI__cpuidex: { + mlir::Type i32Ty = builder.getSInt32Ty(); + mlir::Value subFuncId = builtinID == X86::BI__cpuidex + ? ops[2] + : builder.getConstInt(loc, sInt32Ty, 0); + cir::CpuIdOp::create(builder, loc, i32Ty, /*basePtr=*/ops[0], + /*funcId=*/ops[1], /*subFuncId=*/subFuncId); + return mlir::Value{}; + } case X86::BI__emul: case X86::BI__emulu: case X86::BI__mulh: diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index eb0a219f18618..24924085ea9ea 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -4193,6 +4193,76 @@ mlir::LogicalResult CIRToLLVMAwaitOpLowering::matchAndRewrite( return mlir::failure(); } +mlir::LogicalResult CIRToLLVMCpuIdOpLowering::matchAndRewrite( + cir::CpuIdOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + mlir::Type i32Ty = rewriter.getI32Type(); + mlir::Type i64Ty = rewriter.getI64Type(); + mlir::Type i32PtrTy = mlir::LLVM::LLVMPointerType::get(i32Ty.getContext(), 0); + + mlir::Type cpuidRetTy = mlir::LLVM::LLVMStructType::getLiteral( + rewriter.getContext(), {i32Ty, i32Ty, i32Ty, i32Ty}); + + mlir::Value funcId = adaptor.getFuncId(); + mlir::Value subFuncId = adaptor.getSubFuncId(); + mlir::StringAttr opNameAttr = op->getAttrOfType<mlir::StringAttr>("name"); + if (!opNameAttr) + return mlir::failure(); + if (opNameAttr.getValue() == "cpuid") + subFuncId = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), i32Ty, 0); + std::vector operands{funcId, subFuncId}; + + StringRef asmString, constraints; + mlir::ModuleOp moduleOp = op->getParentOfType<mlir::ModuleOp>(); + mlir::StringAttr tripleAttr = + moduleOp->getAttrOfType<mlir::StringAttr>("llvm.target_triple"); + if (!tripleAttr) + return mlir::failure(); + llvm::Triple triple(tripleAttr.getValue().str()); + if (triple.getArch() == llvm::Triple::x86) { + asmString = "cpuid"; + constraints = "={ax},={bx},={cx},={dx},{ax},{cx}"; + } else { + // x86-64 uses %rbx as the base register, so preserve it. + asmString = "xchgq %rbx, ${1:q}\n" + "cpuid\n" + "xchgq %rbx, ${1:q}"; + constraints = "={ax},=r,={cx},={dx},0,2"; + } + + mlir::Value inlineAsm = + mlir::LLVM::InlineAsmOp::create( + rewriter, op.getLoc(), cpuidRetTy, mlir::ValueRange(operands), + rewriter.getStringAttr(asmString), + rewriter.getStringAttr(constraints), + /*has_side_effects=*/mlir::UnitAttr{}, + /*is_align_stack=*/mlir::UnitAttr{}, + /*tail_call_kind=*/mlir::LLVM::TailCallKindAttr{}, + /*asm_dialect=*/mlir::LLVM::AsmDialectAttr{}, + /*operand_attrs=*/mlir::ArrayAttr{}) + .getResult(0); + + mlir::Value basePtr = adaptor.getBasePtr(); + + mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>()); + unsigned alignment = layout.getTypeABIAlignment(i32Ty); + for (unsigned i = 0; i < 4; i++) { + mlir::Value extracted = + mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(), inlineAsm, i) + .getResult(); + mlir::Value index = mlir::LLVM::ConstantOp::create( + rewriter, op.getLoc(), i64Ty, rewriter.getI64IntegerAttr(i)); + llvm::SmallVector<mlir::Value, 1> gepIndices = {index}; + mlir::Value storePtr = mlir::LLVM::GEPOp::create( + rewriter, op.getLoc(), i32PtrTy, i32Ty, basePtr, + gepIndices, mlir::LLVM::GEPNoWrapFlags::none) + .getResult(); + mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), extracted, storePtr, + alignment); + } + return mlir::success(); +} + std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() { return std::make_unique<ConvertCIRToLLVMPass>(); } 
@llvmbot
Copy link
Member

llvmbot commented Dec 21, 2025

@llvm/pr-subscribers-clangir

Author: Roberto Turrado Camblor (rturrado)

Changes

Adds support for the __builtin_ia32_cpuid and __builtin_ia32_cpuidex X86 builtins.

Part of 167765.


Full diff: https://github.com/llvm/llvm-project/pull/173197.diff

3 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+27)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp (+10-1)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+70)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 7e7424fd71878..c1292ac6ce241 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -5762,4 +5762,31 @@ def CIR_BlockAddressOp : CIR_Op<"block_address", [Pure]> { }]; } +//===----------------------------------------------------------------------===// +// CpuIdOp +//===----------------------------------------------------------------------===// + +def CIR_CpuIdOp : CIR_Op<"cpuid"> { + let summary = "Get information about the CPU"; + let description = [{ + The `cir.cpuid` operation takes a base pointer to an array of 4 integers, a + function ID and a sub-function ID. The array of 4 integers is filled with + different information about the processor. + + Example: + + ```mlir + cir.cpuid %basePtr, %funcId, %subFuncId + : (!cir.ptr<!cir.array<4 x !s32i>>, !s32i, !s32i) -> + ``` + }]; + + let arguments = + (ins Arg<CIR_PtrToArray, "array address", [MemWrite]>:$basePtr, + CIR_SInt32:$funcId, CIR_SInt32:$subFuncId); + // TODO: remove once we can return an optional mlir::Value from + // emitX86BuiltinExpr + let results = (outs CIR_VectorType:$result); +} + #endif // CLANG_CIR_DIALECT_IR_CIROPS_TD diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp index 1c87e945de846..c58391ecc5b42 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp @@ -25,6 +25,7 @@ #include "clang/CIR/Dialect/IR/CIRTypes.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/ADT/Sequence.h" +#include "llvm/IR/InlineAsm.h" #include "llvm/Support/ErrorHandling.h" #include <string> @@ -1835,7 +1836,15 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) { case X86::BI__builtin_ia32_cvtneps2bf16_256_mask: case X86::BI__builtin_ia32_cvtneps2bf16_512_mask: case X86::BI__cpuid: - case X86::BI__cpuidex: + case X86::BI__cpuidex: { + mlir::Type i32Ty = builder.getSInt32Ty(); + mlir::Value subFuncId = builtinID == X86::BI__cpuidex + ? ops[2] + : builder.getConstInt(loc, sInt32Ty, 0); + cir::CpuIdOp::create(builder, loc, i32Ty, /*basePtr=*/ops[0], + /*funcId=*/ops[1], /*subFuncId=*/subFuncId); + return mlir::Value{}; + } case X86::BI__emul: case X86::BI__emulu: case X86::BI__mulh: diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index eb0a219f18618..24924085ea9ea 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -4193,6 +4193,76 @@ mlir::LogicalResult CIRToLLVMAwaitOpLowering::matchAndRewrite( return mlir::failure(); } +mlir::LogicalResult CIRToLLVMCpuIdOpLowering::matchAndRewrite( + cir::CpuIdOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + mlir::Type i32Ty = rewriter.getI32Type(); + mlir::Type i64Ty = rewriter.getI64Type(); + mlir::Type i32PtrTy = mlir::LLVM::LLVMPointerType::get(i32Ty.getContext(), 0); + + mlir::Type cpuidRetTy = mlir::LLVM::LLVMStructType::getLiteral( + rewriter.getContext(), {i32Ty, i32Ty, i32Ty, i32Ty}); + + mlir::Value funcId = adaptor.getFuncId(); + mlir::Value subFuncId = adaptor.getSubFuncId(); + mlir::StringAttr opNameAttr = op->getAttrOfType<mlir::StringAttr>("name"); + if (!opNameAttr) + return mlir::failure(); + if (opNameAttr.getValue() == "cpuid") + subFuncId = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), i32Ty, 0); + std::vector operands{funcId, subFuncId}; + + StringRef asmString, constraints; + mlir::ModuleOp moduleOp = op->getParentOfType<mlir::ModuleOp>(); + mlir::StringAttr tripleAttr = + moduleOp->getAttrOfType<mlir::StringAttr>("llvm.target_triple"); + if (!tripleAttr) + return mlir::failure(); + llvm::Triple triple(tripleAttr.getValue().str()); + if (triple.getArch() == llvm::Triple::x86) { + asmString = "cpuid"; + constraints = "={ax},={bx},={cx},={dx},{ax},{cx}"; + } else { + // x86-64 uses %rbx as the base register, so preserve it. + asmString = "xchgq %rbx, ${1:q}\n" + "cpuid\n" + "xchgq %rbx, ${1:q}"; + constraints = "={ax},=r,={cx},={dx},0,2"; + } + + mlir::Value inlineAsm = + mlir::LLVM::InlineAsmOp::create( + rewriter, op.getLoc(), cpuidRetTy, mlir::ValueRange(operands), + rewriter.getStringAttr(asmString), + rewriter.getStringAttr(constraints), + /*has_side_effects=*/mlir::UnitAttr{}, + /*is_align_stack=*/mlir::UnitAttr{}, + /*tail_call_kind=*/mlir::LLVM::TailCallKindAttr{}, + /*asm_dialect=*/mlir::LLVM::AsmDialectAttr{}, + /*operand_attrs=*/mlir::ArrayAttr{}) + .getResult(0); + + mlir::Value basePtr = adaptor.getBasePtr(); + + mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>()); + unsigned alignment = layout.getTypeABIAlignment(i32Ty); + for (unsigned i = 0; i < 4; i++) { + mlir::Value extracted = + mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(), inlineAsm, i) + .getResult(); + mlir::Value index = mlir::LLVM::ConstantOp::create( + rewriter, op.getLoc(), i64Ty, rewriter.getI64IntegerAttr(i)); + llvm::SmallVector<mlir::Value, 1> gepIndices = {index}; + mlir::Value storePtr = mlir::LLVM::GEPOp::create( + rewriter, op.getLoc(), i32PtrTy, i32Ty, basePtr, + gepIndices, mlir::LLVM::GEPNoWrapFlags::none) + .getResult(); + mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), extracted, storePtr, + alignment); + } + return mlir::success(); +} + std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() { return std::make_unique<ConvertCIRToLLVMPass>(); } 
@rturrado
Copy link
Contributor Author

rturrado commented Dec 21, 2025

I haven't added any tests yet.

I have seen there are currently 2 types of tests for cpuid/cpuidex:

  1. We have clang/test/Headers/cpuid.c which #include <cpuid.h> from clang/lib/Headers, but this file defines __cpuid(__leaf, __eax, __ebx, __ecx, __edx) and __cpuid_count(__leaf, __count, __eax, __ebx, __ecx, __edx) directly as __asm inline code. And implements __cpuidex(int __cpu_info[4], int __leaf, int __subleaf) by using __cpuid_count. I don't think the new CIR builtins should cover these cases.

  2. And then we have clang/test/CodeGen/ms-intrinsics-cpuid.c, which #include <intrin.h> from clang/lib/Headers, and test __cpuid(int cpuInfo[4], int function_id) and __cpuidex(int cpuInfo[4], int function_id, int subfunction_id), but only for Windows/MSVC. I think the new CIR builtins should cover these cases, but for Linux. However, there doesn't seem to be a header file, either clang/lib/Headers/immintrin.h or one accessible through this one, that implements __cpuid/__cpuidex. Should we add this file and builtins as part of this PR?

Thanks!

@github-actions
Copy link

github-actions bot commented Dec 21, 2025

🐧 Linux x64 Test Results

  • 113092 tests passed
  • 4099 tests skipped

✅ The build succeeded and all tests passed.

@rturrado rturrado force-pushed the 167765_cpuid branch 2 times, most recently from 980272e to ebcbcc3 Compare December 21, 2025 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

2 participants