diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 16258513239d9..2124b1dc62a81 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -413,6 +413,18 @@ def CIR_ConstantOp : CIR_Op<"const", [ template T getValueAttr() { return mlir::dyn_cast(getValue()); } + + llvm::APInt getIntValue() { + if (const auto intAttr = getValueAttr()) + return intAttr.getValue(); + llvm_unreachable("Expected an IntAttr in ConstantOp"); + } + + bool getBoolValue() { + if (const auto boolAttr = getValueAttr()) + return boolAttr.getValue(); + llvm_unreachable("Expected a BoolAttr in ConstantOp"); + } }]; let hasFolder = 1; @@ -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:$result); + let arguments = (ins + StrAttr:$intrinsic_name, Variadic:$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 //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index af1ffffcf54c0..9cb069bfec626 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -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; } diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp index 0198a9d4eb192..02390cabcee35 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp @@ -21,6 +21,19 @@ using namespace clang; using namespace clang::CIRGen; +template +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(op)...) + .getResult(); +} + mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *e) { if (builtinID == Builtin::BI__builtin_cpu_is) { @@ -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 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: diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 5eba5ba6c3df1..93aacba15f0b4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1430,6 +1430,28 @@ mlir::Value CIRGenFunction::emitPromotedScalarExpr(const Expr *e, return ScalarExprEmitter(*this, builder).Visit(const_cast(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 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 diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index f879e580989f7..f413bf88c4e8e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -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 *arg); + mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv, cir::UnaryOpKind kind, bool isPre); diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index b4afed7019417..d88a4ad76f27b 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -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(); diff --git a/clang/test/CIR/CodeGen/X86/sse-builtins.c b/clang/test/CIR/CodeGen/X86/sse-builtins.c new file mode 100644 index 0000000000000..3a61018741958 --- /dev/null +++ b/clang/test/CIR/CodeGen/X86/sse-builtins.c @@ -0,0 +1,28 @@ +// 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 + +// 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-prefix=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-prefix=OGCG + +// This test mimics clang/test/CodeGen/X86/sse-builtins.c, which eventually +// CIR shall be able to support fully. + +#include + + +void test_mm_sfence(void) { + // CIR-LABEL: test_mm_sfence + // LLVM-LABEL: test_mm_sfence + // OGCG-LABEL: test_mm_sfence + _mm_sfence(); + // CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse.sfence" : () -> !void + // LLVM: call void @llvm.x86.sse.sfence() + // OGCG: call void @llvm.x86.sse.sfence() +} diff --git a/clang/test/CIR/CodeGen/X86/sse2-builtins.c b/clang/test/CIR/CodeGen/X86/sse2-builtins.c new file mode 100644 index 0000000000000..144ca143fbf15 --- /dev/null +++ b/clang/test/CIR/CodeGen/X86/sse2-builtins.c @@ -0,0 +1,58 @@ +// 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 --input-file=%t.cir %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-cir -o %t.cir -Wall -Werror +// RUN: FileCheck --check-prefixes=CIR --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 --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 --input-file=%t.ll %s + +// 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-prefix=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-prefix=OGCG + +// This test mimics clang/test/CodeGen/X86/sse2-builtins.c, which eventually +// CIR shall be able to support fully. + +#include + + +void test_mm_clflush(void* A) { + // CIR-LABEL: test_mm_clflush + // LLVM-LABEL: test_mm_clflush + // OGCG-LABEL: test_mm_clflush + _mm_clflush(A); + // CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.clflush" {{%.*}} : (!cir.ptr) -> !void + // LLVM: call void @llvm.x86.sse2.clflush(ptr {{%.*}}) + // OGCG: call void @llvm.x86.sse2.clflush(ptr {{%.*}}) +} + +void test_mm_lfence(void) { + // CIR-LABEL: test_mm_lfence + // LLVM-LABEL: test_mm_lfence + // OGCG-LABEL: test_mm_lfence + _mm_lfence(); + // CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.lfence" : () -> !void + // LLVM: call void @llvm.x86.sse2.lfence() + // OGCG: call void @llvm.x86.sse2.lfence() +} + +void test_mm_mfence(void) { + // CIR-LABEL: test_mm_mfence + // LLVM-LABEL: test_mm_mfence + // OGCG-LABEL: test_mm_mfence + _mm_mfence(); + // CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.mfence" : () -> !void + // LLVM: call void @llvm.x86.sse2.mfence() + // OGCG: call void @llvm.x86.sse2.mfence() +} + +void test_mm_pause(void) { + // CIR-LABEL: test_mm_pause + // LLVM-LABEL: test_mm_pause + // OGCG-LABEL: test_mm_pause + _mm_pause(); + // CIR: {{%.*}} = cir.call_llvm_intrinsic "x86.sse2.pause" : () -> !void + // LLVM: call void @llvm.x86.sse2.pause() + // OGCG: call void @llvm.x86.sse2.pause() +}