Skip to content

Commit c562999

Browse files
Implement ClangIR codegen for SSE builtins
1 parent 938f521 commit c562999

File tree

8 files changed

+294
-12
lines changed

8 files changed

+294
-12
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,12 @@ def CIR_ConstantOp : CIR_Op<"const", [
413413

414414
template <typename T>
415415
T getValueAttr() { return mlir::dyn_cast<T>(getValue()); }
416+
417+
llvm::APInt getIntValue() {
418+
if (const auto intAttr = getValueAttr<cir::IntAttr>())
419+
return intAttr.getValue();
420+
llvm_unreachable("Expected an IntAttr in ConstantOp");
421+
}
416422
}];
417423

418424
let hasFolder = 1;
@@ -2579,6 +2585,40 @@ def CIR_FuncOp : CIR_Op<"func", [
25792585
}];
25802586
}
25812587

2588+
//===----------------------------------------------------------------------===//
2589+
// LLVMIntrinsicCallOp
2590+
//===----------------------------------------------------------------------===//
2591+
2592+
def CIR_LLVMIntrinsicCallOp : CIR_Op<"llvm.intrinsic"> {
2593+
let summary = "Call to llvm intrinsic functions that is not defined in CIR";
2594+
let description = [{
2595+
`cir.llvm.intrinsic` operation represents a call-like expression which has
2596+
return type and arguments that maps directly to a llvm intrinsic.
2597+
It only records intrinsic `intrinsic_name`.
2598+
}];
2599+
2600+
let results = (outs Optional<CIR_AnyType>:$result);
2601+
let arguments = (ins
2602+
StrAttr:$intrinsic_name, Variadic<CIR_AnyType>:$arg_ops);
2603+
2604+
let skipDefaultBuilders = 1;
2605+
2606+
let assemblyFormat = [{
2607+
$intrinsic_name $arg_ops `:` functional-type($arg_ops, $result) attr-dict
2608+
}];
2609+
2610+
let builders = [
2611+
OpBuilder<(ins "mlir::StringAttr":$intrinsic_name, "mlir::Type":$resType,
2612+
CArg<"mlir::ValueRange", "{}">:$operands), [{
2613+
$_state.addAttribute("intrinsic_name", intrinsic_name);
2614+
$_state.addOperands(operands);
2615+
if (resType)
2616+
$_state.addTypes(resType);
2617+
}]>,
2618+
];
2619+
2620+
}
2621+
25822622
//===----------------------------------------------------------------------===//
25832623
// CallOp
25842624
//===----------------------------------------------------------------------===//

clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,22 @@ CIRGenFunction::emitTargetBuiltinExpr(unsigned builtinID, const CallExpr *e,
625625
getTarget().getTriple().getArch());
626626
}
627627

628+
mlir::Value CIRGenFunction::emitScalarOrConstFoldImmArg(
629+
const unsigned ICEArguments, const unsigned idx, const CallExpr *e) {
630+
mlir::Value arg = {};
631+
if ((ICEArguments & (1 << idx)) == 0) {
632+
arg = emitScalarExpr(e->getArg(idx));
633+
} else {
634+
// If this is required to be a constant, constant fold it so that we
635+
// know that the generated intrinsic gets a ConstantInt.
636+
const std::optional<llvm::APSInt> result =
637+
e->getArg(idx)->getIntegerConstantExpr(getContext());
638+
assert(result && "Expected argument to be a constant");
639+
arg = builder.getConstInt(getLoc(e->getSourceRange()), *result);
640+
}
641+
return arg;
642+
}
643+
628644
/// Given a builtin id for a function like "__builtin_fabsf", return a Function*
629645
/// for "fabsf".
630646
cir::FuncOp CIRGenModule::getBuiltinLibFunction(const FunctionDecl *fd,

clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,22 @@
1818
#include "clang/CIR/MissingFeatures.h"
1919
#include "llvm/IR/IntrinsicsX86.h"
2020

21+
#define UNIMPLEMENTED_BUILTIN() \
22+
do { \
23+
cgm.errorNYI(e->getSourceRange(), \
24+
std::string("unimplemented X86 builtin call: ") + \
25+
getContext().BuiltinInfo.getName(builtinID)); \
26+
return {}; \
27+
} while (0)
28+
2129
using namespace clang;
2230
using namespace clang::CIRGen;
2331

32+
/// Get integer from a mlir::Value that is an int constant or a constant op.
33+
static int64_t getIntValueFromConstOp(mlir::Value val) {
34+
return val.getDefiningOp<cir::ConstantOp>().getIntValue().getSExtValue();
35+
}
36+
2437
mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID,
2538
const CallExpr *e) {
2639
if (builtinID == Builtin::BI__builtin_cpu_is) {
@@ -43,15 +56,76 @@ mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID,
4356
// Find out if any arguments are required to be integer constant expressions.
4457
assert(!cir::MissingFeatures::handleBuiltinICEArguments());
4558

59+
llvm::SmallVector<mlir::Value, 4> ops;
60+
61+
// Find out if any arguments are required to be integer constant expressions.
62+
unsigned ICEArguments = 0;
63+
ASTContext::GetBuiltinTypeError error;
64+
getContext().GetBuiltinType(builtinID, error, &ICEArguments);
65+
assert(error == ASTContext::GE_None && "Should not codegen an error");
66+
67+
for (auto [idx, _] : llvm::enumerate(e->arguments())) {
68+
ops.push_back(emitScalarOrConstFoldImmArg(ICEArguments, idx, e));
69+
}
70+
4671
switch (builtinID) {
4772
default:
4873
return {};
49-
case X86::BI_mm_prefetch:
50-
case X86::BI_mm_clflush:
51-
case X86::BI_mm_lfence:
52-
case X86::BI_mm_pause:
53-
case X86::BI_mm_mfence:
54-
case X86::BI_mm_sfence:
74+
case X86::BI_mm_prefetch: {
75+
mlir::Value address = builder.createPtrBitcast(ops[0], voidTy);
76+
77+
int64_t hint = getIntValueFromConstOp(ops[1]);
78+
mlir::Value rw =
79+
cir::ConstantOp::create(builder, getLoc(e->getExprLoc()),
80+
cir::IntAttr::get(sInt32Ty, (hint >> 2) & 0x1));
81+
mlir::Value locality =
82+
cir::ConstantOp::create(builder, getLoc(e->getExprLoc()),
83+
cir::IntAttr::get(sInt32Ty, hint & 0x3));
84+
mlir::Value data = cir::ConstantOp::create(builder, getLoc(e->getExprLoc()),
85+
cir::IntAttr::get(sInt32Ty, 1));
86+
mlir::Type voidTy = cir::VoidType::get(&getMLIRContext());
87+
88+
return cir::LLVMIntrinsicCallOp::create(
89+
builder, getLoc(e->getExprLoc()),
90+
builder.getStringAttr("prefetch"), voidTy,
91+
mlir::ValueRange{address, rw, locality, data})
92+
.getResult();
93+
}
94+
case X86::BI_mm_clflush: {
95+
mlir::Type voidTy = cir::VoidType::get(&getMLIRContext());
96+
return cir::LLVMIntrinsicCallOp::create(
97+
builder, getLoc(e->getExprLoc()),
98+
builder.getStringAttr("x86.sse2.clflush"), voidTy, ops[0])
99+
.getResult();
100+
}
101+
case X86::BI_mm_lfence: {
102+
mlir::Type voidTy = cir::VoidType::get(&getMLIRContext());
103+
return cir::LLVMIntrinsicCallOp::create(
104+
builder, getLoc(e->getExprLoc()),
105+
builder.getStringAttr("x86.sse2.lfence"), voidTy)
106+
.getResult();
107+
}
108+
case X86::BI_mm_pause: {
109+
mlir::Type voidTy = cir::VoidType::get(&getMLIRContext());
110+
return cir::LLVMIntrinsicCallOp::create(
111+
builder, getLoc(e->getExprLoc()),
112+
builder.getStringAttr("x86.sse2.pause"), voidTy)
113+
.getResult();
114+
}
115+
case X86::BI_mm_mfence: {
116+
mlir::Type voidTy = cir::VoidType::get(&getMLIRContext());
117+
return cir::LLVMIntrinsicCallOp::create(
118+
builder, getLoc(e->getExprLoc()),
119+
builder.getStringAttr("x86.sse2.mfence"), voidTy)
120+
.getResult();
121+
}
122+
case X86::BI_mm_sfence: {
123+
mlir::Type voidTy = cir::VoidType::get(&getMLIRContext());
124+
return cir::LLVMIntrinsicCallOp::create(
125+
builder, getLoc(e->getExprLoc()),
126+
builder.getStringAttr("x86.sse.sfence"), voidTy)
127+
.getResult();
128+
}
55129
case X86::BI__rdtsc:
56130
case X86::BI__builtin_ia32_rdtscp:
57131
case X86::BI__builtin_ia32_lzcnt_u16:
@@ -82,10 +156,27 @@ mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID,
82156
case X86::BI__builtin_ia32_vec_set_v16hi:
83157
case X86::BI__builtin_ia32_vec_set_v8si:
84158
case X86::BI__builtin_ia32_vec_set_v4di:
159+
UNIMPLEMENTED_BUILTIN();
85160
case X86::BI_mm_setcsr:
86-
case X86::BI__builtin_ia32_ldmxcsr:
161+
case X86::BI__builtin_ia32_ldmxcsr: {
162+
Address tmp =
163+
createMemTemp(e->getArg(0)->getType(), getLoc(e->getExprLoc()));
164+
builder.createStore(getLoc(e->getExprLoc()), ops[0], tmp);
165+
return cir::LLVMIntrinsicCallOp::create(
166+
builder, getLoc(e->getExprLoc()),
167+
builder.getStringAttr("x86.sse.ldmxcsr"), builder.getVoidTy(),
168+
tmp.getPointer())
169+
.getResult();
170+
}
87171
case X86::BI_mm_getcsr:
88-
case X86::BI__builtin_ia32_stmxcsr:
172+
case X86::BI__builtin_ia32_stmxcsr: {
173+
Address tmp = createMemTemp(e->getType(), getLoc(e->getExprLoc()));
174+
cir::LLVMIntrinsicCallOp::create(builder, getLoc(e->getExprLoc()),
175+
builder.getStringAttr("x86.sse.stmxcsr"),
176+
builder.getVoidTy(), tmp.getPointer())
177+
.getResult();
178+
return builder.createLoad(getLoc(e->getExprLoc()), tmp);
179+
}
89180
case X86::BI__builtin_ia32_xsave:
90181
case X86::BI__builtin_ia32_xsave64:
91182
case X86::BI__builtin_ia32_xrstor:
@@ -798,9 +889,6 @@ mlir::Value CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID,
798889
case X86::BI__builtin_ia32_vfcmaddcsh_round_mask3:
799890
case X86::BI__builtin_ia32_vfmaddcsh_round_mask3:
800891
case X86::BI__builtin_ia32_prefetchi:
801-
cgm.errorNYI(e->getSourceRange(),
802-
std::string("unimplemented X86 builtin call: ") +
803-
getContext().BuiltinInfo.getName(builtinID));
804-
return {};
892+
UNIMPLEMENTED_BUILTIN();
805893
}
806894
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,9 @@ class CIRGenFunction : public CIRGenTypeCache {
16991699
void emitScalarInit(const clang::Expr *init, mlir::Location loc,
17001700
LValue lvalue, bool capturedByInit = false);
17011701

1702+
mlir::Value emitScalarOrConstFoldImmArg(unsigned ICEArguments, unsigned idx,
1703+
const CallExpr *e);
1704+
17021705
void emitStaticVarDecl(const VarDecl &d, cir::GlobalLinkageKind linkage);
17031706

17041707
void emitStoreOfComplex(mlir::Location loc, mlir::Value v, LValue dest,

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,29 @@ mlir::LogicalResult CIRToLLVMASinOpLowering::matchAndRewrite(
646646
return mlir::success();
647647
}
648648

649+
mlir::LogicalResult CIRToLLVMLLVMIntrinsicCallOpLowering::matchAndRewrite(
650+
cir::LLVMIntrinsicCallOp op, OpAdaptor adaptor,
651+
mlir::ConversionPatternRewriter &rewriter) const {
652+
mlir::Type llvmResTy =
653+
getTypeConverter()->convertType(op->getResultTypes()[0]);
654+
if (!llvmResTy)
655+
return op.emitError("expected LLVM result type");
656+
StringRef name = op.getIntrinsicName();
657+
// Some llvm intrinsics require ElementType attribute to be attached to
658+
// the argument of pointer type. That prevents us from generating LLVM IR
659+
// because from LLVM dialect, we have LLVM IR like the below which fails
660+
// LLVM IR verification.
661+
// %3 = call i64 @llvm.aarch64.ldxr.p0(ptr %2)
662+
// The expected LLVM IR should be like
663+
// %3 = call i64 @llvm.aarch64.ldxr.p0(ptr elementtype(i32) %2)
664+
// TODO(cir): MLIR LLVM dialect should handle this part as CIR has no way
665+
// to set LLVM IR attribute.
666+
assert(!cir::MissingFeatures::llvmIntrinsicElementTypeSupport());
667+
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm." + name, llvmResTy,
668+
adaptor.getOperands());
669+
return mlir::success();
670+
}
671+
649672
mlir::LogicalResult CIRToLLVMAssumeOpLowering::matchAndRewrite(
650673
cir::AssumeOp op, OpAdaptor adaptor,
651674
mlir::ConversionPatternRewriter &rewriter) const {

clang/test/CIR/CodeGen/X86/pause.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %clang_cc1 -ffreestanding -triple x86_64-unknown-linux -Wno-implicit-function-declaration -target-feature +sse2 -fclangir -emit-cir -o %t.cir %s
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -ffreestanding -triple x86_64-unknown-linux -Wno-implicit-function-declaration -target-feature +sse2 -fclangir -emit-llvm -o %t.ll %s
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
5+
6+
// RUN: %clang_cc1 -ffreestanding -triple x86_64-unknown-linux -Wno-implicit-function-declaration -target-feature -sse2 -fclangir -emit-cir -o %t.cir %s
7+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
8+
// RUN: %clang_cc1 -ffreestanding -triple x86_64-unknown-linux -Wno-implicit-function-declaration -target-feature -sse2 -fclangir -emit-llvm -o %t.ll %s
9+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
10+
11+
// This test mimics clang/test/CodeGen/X86/pause.c, which eventually
12+
// CIR shall be able to support fully.
13+
14+
#include <x86intrin.h>
15+
16+
void test_mm_pause(void) {
17+
// CIR-LABEL: test_mm_pause
18+
// LLVM-LABEL: test_mm_pause
19+
_mm_pause();
20+
// CIR: {{%.*}} = cir.llvm.intrinsic "x86.sse2.pause" : () -> !void
21+
// LLVM: call void @llvm.x86.sse2.pause()
22+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// 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
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// 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
4+
// RUN: FileCheck --check-prefixes=LLVM --input-file=%t.ll %s
5+
6+
// 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
7+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
8+
// 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
9+
// RUN: FileCheck --check-prefixes=LLVM --input-file=%t.ll %s
10+
11+
// This test mimics clang/test/CodeGen/X86/sse-builtins.c, which eventually
12+
// CIR shall be able to support fully.
13+
14+
#include <immintrin.h>
15+
16+
void test_mm_prefetch(char const* p) {
17+
// CIR-LABEL: test_mm_prefetch
18+
// LLVM-LABEL: test_mm_prefetch
19+
_mm_prefetch(p, 0);
20+
// CIR: cir.prefetch read locality(0) %{{.*}} : !cir.ptr<!void>
21+
// LLVM: call void @llvm.prefetch.p0(ptr {{.*}}, i32 0, i32 0, i32 1)
22+
}
23+
24+
void test_mm_sfence(void) {
25+
// CIR-LABEL: test_mm_sfence
26+
// LLVM-LABEL: test_mm_sfence
27+
_mm_sfence();
28+
// CIR: {{%.*}} = cir.llvm.intrinsic "x86.sse.sfence" : () -> !void
29+
// LLVM: call void @llvm.x86.sse.sfence()
30+
}
31+
32+
void test_mm_setcsr(unsigned int A) {
33+
// CIR-LABEL: test_mm_setcsr
34+
// CIR: cir.store {{.*}}, {{.*}} : !u32i
35+
// CIR: cir.llvm.intrinsic "x86.sse.ldmxcsr" {{.*}} : (!cir.ptr<!u32i>) -> !void
36+
37+
// LLVM-LABEL: test_mm_setcsr
38+
// LLVM: store i32
39+
// LLVM: call void @llvm.x86.sse.ldmxcsr(ptr {{.*}})
40+
_mm_setcsr(A);
41+
}
42+
43+
unsigned int test_mm_getcsr(void) {
44+
// CIR-LABEL: test_mm_getcsr
45+
// CIR: cir.llvm.intrinsic "x86.sse.stmxcsr" %{{.*}} : (!cir.ptr<!u32i>) -> !void
46+
// CIR: cir.load {{.*}} : !cir.ptr<!u32i>, !u32i
47+
48+
// LLVM-LABEL: test_mm_getcsr
49+
// LLVM: call void @llvm.x86.sse.stmxcsr(ptr %{{.*}})
50+
// LLVM: load i32
51+
return _mm_getcsr();
52+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// 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
2+
// RUN: FileCheck --check-prefixes=CIR-CHECK --input-file=%t.cir %s
3+
// 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
4+
// RUN: FileCheck --check-prefixes=CIR-CHECK --input-file=%t.cir %s
5+
6+
// 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
7+
// RUN: FileCheck --check-prefixes=LLVM-CHECK --input-file=%t.ll %s
8+
// 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
9+
// RUN: FileCheck --check-prefixes=LLVM-CHECK --input-file=%t.ll %s
10+
11+
// This test mimics clang/test/CodeGen/X86/sse2-builtins.c, which eventually
12+
// CIR shall be able to support fully.
13+
14+
#include <immintrin.h>
15+
16+
void test_mm_clflush(void* A) {
17+
// CIR-LABEL: test_mm_clflush
18+
// LLVM-LABEL: test_mm_clflush
19+
_mm_clflush(A);
20+
// CIR-CHECK: {{%.*}} = cir.llvm.intrinsic "x86.sse2.clflush" {{%.*}} : (!cir.ptr<!void>) -> !void
21+
// LLVM-CHECK: call void @llvm.x86.sse2.clflush(ptr {{%.*}})
22+
}
23+
24+
void test_mm_lfence(void) {
25+
// CIR-CHECK-LABEL: test_mm_lfence
26+
// LLVM-CHECK-LABEL: test_mm_lfence
27+
_mm_lfence();
28+
// CIR-CHECK: {{%.*}} = cir.llvm.intrinsic "x86.sse2.lfence" : () -> !void
29+
// LLVM-CHECK: call void @llvm.x86.sse2.lfence()
30+
}
31+
32+
void test_mm_mfence(void) {
33+
// CIR-CHECK-LABEL: test_mm_mfence
34+
// LLVM-CHECK-LABEL: test_mm_mfence
35+
_mm_mfence();
36+
// CIR-CHECK: {{%.*}} = cir.llvm.intrinsic "x86.sse2.mfence" : () -> !void
37+
// LLVM-CHECK: call void @llvm.x86.sse2.mfence()
38+
}

0 commit comments

Comments
 (0)