Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
45 changes: 45 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,18 @@ def CIR_ConstantOp : CIR_Op<"const", [

template <typename T>
T getValueAttr() { return mlir::dyn_cast<T>(getValue()); }

llvm::APInt getIntValue() {
if (const auto intAttr = getValueAttr<cir::IntAttr>())
return intAttr.getValue();
llvm_unreachable("Expected an IntAttr in ConstantOp");
}

bool getBoolValue() {
if (const auto boolAttr = getValueAttr<cir::BoolAttr>())
return boolAttr.getValue();
llvm_unreachable("Expected a BoolAttr in ConstantOp");
}
}];

let hasFolder = 1;
Expand Down Expand Up @@ -2579,6 +2591,39 @@ def CIR_FuncOp : CIR_Op<"func", [
}];
}

//===----------------------------------------------------------------------===//
// LLVMIntrinsicCallOp
//===----------------------------------------------------------------------===//

def CIR_LLVMIntrinsicCallOp : CIR_Op<"call_llvm_intrinsic"> {
let summary = "Call to llvm intrinsic functions that is not defined in CIR";
let description = [{
`cir.call_llvm_intrinsic` operation represents a call-like expression which has
return type and arguments that maps directly to a llvm intrinsic.
It only records intrinsic `intrinsic_name`.
}];

let results = (outs Optional<CIR_AnyType>:$result);
let arguments = (ins
StrAttr:$intrinsic_name, Variadic<CIR_AnyType>:$arg_ops);

let skipDefaultBuilders = 1;

let assemblyFormat = [{
$intrinsic_name $arg_ops `:` functional-type($arg_ops, $result) attr-dict
}];

let builders = [
OpBuilder<(ins "mlir::StringAttr":$intrinsic_name, "mlir::Type":$resType,
CArg<"mlir::ValueRange", "{}">:$operands), [{
$_state.addAttribute("intrinsic_name", intrinsic_name);
$_state.addOperands(operands);
if (resType)
$_state.addTypes(resType);
}]>,
];
}

//===----------------------------------------------------------------------===//
// CallOp
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ struct MissingFeatures {
static bool innermostEHScope() { return false; }
static bool insertBuiltinUnpredictable() { return false; }
static bool instrumentation() { return false; }
static bool intrinsicElementTypeSupport() { return false; }
static bool intrinsics() { return false; }
static bool isMemcpyEquivalentSpecialMember() { return false; }
static bool isTrivialCtorOrDtor() { return false; }
Expand Down
37 changes: 36 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
using namespace clang;
using namespace clang::CIRGen;

template <typename... Operands>
static mlir::Value emitIntrinsicCallOp(CIRGenFunction &cgf, const CallExpr *e,
const std::string &str,
const mlir::Type &resTy,
Operands &&...op) {
CIRGenBuilderTy &builder = cgf.getBuilder();
mlir::Location location = cgf.getLoc(e->getExprLoc());
return cir::LLVMIntrinsicCallOp::create(builder, location,
builder.getStringAttr(str), resTy,
std::forward<Operands>(op)...)
.getResult();
}

mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID,
const CallExpr *e) {
if (builtinID == Builtin::BI__builtin_cpu_is) {
Expand All @@ -43,15 +56,37 @@ mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID,
// Find out if any arguments are required to be integer constant expressions.
assert(!cir::MissingFeatures::handleBuiltinICEArguments());

// The operands of the builtin call
llvm::SmallVector<mlir::Value> ops;

// `ICEArguments` is a bitmap indicating whether the argument at the i-th bit
// is required to be a constant integer expression.
unsigned iceArguments = 0;
ASTContext::GetBuiltinTypeError error;
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(emitScalarOrConstFoldImmArg(iceArguments, idx, arg));
}

CIRGenBuilderTy &builder = getBuilder();
mlir::Type voidTy = builder.getVoidTy();

switch (builtinID) {
default:
return {};
case X86::BI_mm_prefetch:
case X86::BI_mm_clflush:
return emitIntrinsicCallOp(*this, e, "x86.sse2.clflush", voidTy, ops[0]);
case X86::BI_mm_lfence:
return emitIntrinsicCallOp(*this, e, "x86.sse2.lfence", voidTy);
case X86::BI_mm_pause:
return emitIntrinsicCallOp(*this, e, "x86.sse2.pause", voidTy);
case X86::BI_mm_mfence:
return emitIntrinsicCallOp(*this, e, "x86.sse2.mfence", voidTy);
case X86::BI_mm_sfence:
return emitIntrinsicCallOp(*this, e, "x86.sse.sfence", voidTy);
case X86::BI_mm_prefetch:
case X86::BI__rdtsc:
case X86::BI__builtin_ia32_rdtscp:
case X86::BI__builtin_ia32_lzcnt_u16:
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,28 @@ mlir::Value CIRGenFunction::emitPromotedScalarExpr(const Expr *e,
return ScalarExprEmitter(*this, builder).Visit(const_cast<Expr *>(e));
}

mlir::Value CIRGenFunction::emitScalarOrConstFoldImmArg(unsigned iceArguments,
unsigned index,
const Expr *arg) {
mlir::Value result{};

// The bit at the specified index indicates whether the argument is required
// to be a constant integer expression.
bool isArgRequiredToBeConstant = (iceArguments & (1 << index));

if (!isArgRequiredToBeConstant) {
result = emitScalarExpr(arg);
} else {
// If this is required to be a constant, constant fold it so that we
// know that the generated intrinsic gets a ConstantInt.
std::optional<llvm::APSInt> iceOpt =
arg->getIntegerConstantExpr(getContext());
assert(iceOpt && "Expected argument to be a constant");
result = builder.getConstInt(getLoc(arg->getSourceRange()), *iceOpt);
}
return result;
}

[[maybe_unused]] static bool mustVisitNullValue(const Expr *e) {
// If a null pointer expression's type is the C++0x nullptr_t and
// the expression is not a simple literal, it must be evaluated
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,9 @@ class CIRGenFunction : public CIRGenTypeCache {
mlir::Value emitScalarExpr(const clang::Expr *e,
bool ignoreResultAssign = false);

mlir::Value emitScalarOrConstFoldImmArg(unsigned iceArguments, unsigned index,
const Expr *e);

mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv,
cir::UnaryOpKind kind, bool isPre);

Expand Down
24 changes: 24 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,30 @@ static mlir::LLVM::CallIntrinsicOp replaceOpWithCallLLVMIntrinsicOp(
return callIntrinOp;
}

mlir::LogicalResult CIRToLLVMLLVMIntrinsicCallOpLowering::matchAndRewrite(
cir::LLVMIntrinsicCallOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type llvmResTy =
getTypeConverter()->convertType(op->getResultTypes()[0]);
if (!llvmResTy)
return op.emitError("expected LLVM result type");
StringRef name = op.getIntrinsicName();

// Some LLVM intrinsics require ElementType attribute to be attached to
// the argument of pointer type. That prevents us from generating LLVM IR
// because from LLVM dialect, we have LLVM IR like the below which fails
// LLVM IR verification.
// %3 = call i64 @llvm.aarch64.ldxr.p0(ptr %2)
// The expected LLVM IR should be like
// %3 = call i64 @llvm.aarch64.ldxr.p0(ptr elementtype(i32) %2)
// TODO(cir): MLIR LLVM dialect should handle this part as CIR has no way
// to set LLVM IR attribute.
assert(!cir::MissingFeatures::intrinsicElementTypeSupport());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm." + name, llvmResTy,
adaptor.getOperands());
return mlir::success();
}

/// IntAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::IntAttr intAttr) {
mlir::Location loc = parentOp->getLoc();
Expand Down
23 changes: 23 additions & 0 deletions clang/test/CIR/CodeGen/X86/sse-builtins.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %clang_cc1 -x c -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse -fclangir -emit-cir -o %t.cir -Wall -Werror
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -x c -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse -fclangir -emit-llvm -o %t.ll -Wall -Werror
// RUN: FileCheck --check-prefixes=LLVM --input-file=%t.ll %s

// RUN: %clang_cc1 -x c++ -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse -fno-signed-char -fclangir -emit-cir -o %t.cir -Wall -Werror
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -x c++ -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse -fclangir -emit-llvm -o %t.ll -Wall -Werror
// RUN: FileCheck --check-prefixes=LLVM --input-file=%t.ll %s

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also add the OGCG checks. It's useful to manually verify that we're generating equivalent code.

// RUN: %clang_cc1 -x c -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse -emit-llvm -o - -Wall -Werror | FileCheck %s --check-prefixes=OGCG
// RUN: %clang_cc1 -x c++ -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse -emit-llvm -o - -Wall -Werror | FileCheck %s --check-prefixes=OGCG

// This test mimics clang/test/CodeGen/X86/sse-builtins.c, which eventually
// CIR shall be able to support fully.

#include <immintrin.h>


void test_mm_sfence(void) {
// CIR-LABEL: test_mm_sfence
// LLVM-LABEL: test_mm_sfence
_mm_sfence();
// CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse.sfence" : () -> !void
// LLVM: call void @llvm.x86.sse.sfence()
}
47 changes: 47 additions & 0 deletions clang/test/CIR/CodeGen/X86/sse2-builtins.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// RUN: %clang_cc1 -x c -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse2 -fclangir -emit-cir -o %t.cir -Wall -Werror
// RUN: FileCheck --check-prefixes=CIR-CHECK --input-file=%t.cir %s
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// RUN: FileCheck --check-prefixes=CIR-CHECK --input-file=%t.cir %s
// RUN: FileCheck --check-prefixes=CIR --input-file=%t.cir %s

Can you replace all instances of CIR-CHECK with CIR and LLVM-CHECK with LLVM? I'm not sure what the person who implemented that in the incubator was trying to accomplish, but it's not necessary. As currently written, this test will ignore the CIR-LABEL and LLVM-LABEL checks. Those are label checks for the CIR and LLVM prefixes.

Also, please add OGCG checks here too.

Copy link
Contributor Author

@HendrikHuebner HendrikHuebner Nov 13, 2025

Choose a reason for hiding this comment

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

Done. What is the point of adding CIR-LABEL instead of just CIR? I've seen CIR-NEXT and CIR-SAME as well, are there docs about what these do?

Edit: Ahh I found this: https://llvm.org/docs/CommandGuide/FileCheck.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@andykaylor Hi, you mentioned you would like to see this PR merged before the other builtin upstreaming PRs, do you think we can land it now?

// RUN: %clang_cc1 -x c -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse2 -fno-signed-char -fclangir -emit-cir -o %t.cir -Wall -Werror
// RUN: FileCheck --check-prefixes=CIR-CHECK --input-file=%t.cir %s

// RUN: %clang_cc1 -x c++ -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse2 -fclangir -emit-llvm -o %t.ll -Wall -Werror
// RUN: FileCheck --check-prefixes=LLVM-CHECK --input-file=%t.ll %s
// RUN: %clang_cc1 -x c++ -flax-vector-conversions=none -ffreestanding %s -triple=x86_64-unknown-linux -target-feature +sse2 -fno-signed-char -fclangir -emit-llvm -o %t.ll -Wall -Werror
// RUN: FileCheck --check-prefixes=LLVM-CHECK --input-file=%t.ll %s

// This test mimics clang/test/CodeGen/X86/sse2-builtins.c, which eventually
// CIR shall be able to support fully.

#include <immintrin.h>


void test_mm_clflush(void* A) {
// CIR-LABEL: test_mm_clflush
// LLVM-LABEL: teh
_mm_clflush(A);
// CIR-CHECK: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.clflush" {{%.*}} : (!cir.ptr<!void>) -> !void
// LLVM-CHECK: call void @llvm.x86.sse2.clflush(ptr {{%.*}})
}

void test_mm_lfence(void) {
// CIR-CHECK-LABEL: test_mm_lfence
// LLVM-CHECK-LABEL: test_mm_lfence
_mm_lfence();
// CIR-CHECK: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.lfence" : () -> !void
// LLVM-CHECK: call void @llvm.x86.sse2.lfence()
}

void test_mm_mfence(void) {
// CIR-CHECK-LABEL: test_mm_mfence
// LLVM-CHECK-LABEL: test_mm_mfence
_mm_mfence();
// CIR-CHECK: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.mfence" : () -> !void
// LLVM-CHECK: call void @llvm.x86.sse2.mfence()
}

void test_mm_pause(void) {
// CIR-LABEL: test_mm_pause
// LLVM-LABEL: test_mm_pause
_mm_pause();
// CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.pause" : () -> !void
// LLVM: call void @llvm.x86.sse2.pause()
}
Loading