Skip to content

Commit ab3795b

Browse files
committed
[CIR] Add support for ExtVector Bool Type
Implements support for ext_vector_type with bool elements. Bool vectors are represented as integers in CIR (e.g., bool4 uses !cir.int<u, 8>), matching traditional CodeGen's approach. Key changes: - CIRGenTypes: Convert ExtVectorBoolType to integer storage (iN where N = max(num_elements, 8)) - CIRGenExprConst: Pack bool elements into integer bits during constant initialization - CIRGenExprScalar: Handle subscript access by extracting bits from integer - CIRGenExpr: Skip vector optimizations for ExtVectorBoolType in load/store paths Tests added for basic initialization, subscript access, and bitwise operations. ghstack-source-id: b8879c0 Pull-Request: #1998
1 parent d0cac23 commit ab3795b

File tree

5 files changed

+285
-36
lines changed

5 files changed

+285
-36
lines changed

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,14 @@ CIRGenCallee CIRGenFunction::emitCallee(const clang::Expr *E) {
634634

635635
mlir::Value CIRGenFunction::emitToMemory(mlir::Value Value, QualType Ty) {
636636
// Bool has a different representation in memory than in registers.
637+
638+
// ExtVectorBoolType: In ClangIR, ExtVectorBoolType is always represented
639+
// as an integer type (!cir.int<u, N>) throughout the IR, including both
640+
// in registers and in memory. This differs from traditional CodeGen where
641+
// it may exist as a vector type that needs conversion to integer for storage.
642+
// Since we use integer representation consistently, no conversion is needed.
643+
// See CIRGenTypes.cpp:675-683 for the type conversion logic.
644+
637645
return Value;
638646
}
639647

@@ -653,18 +661,21 @@ void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr,
653661

654662
auto eltTy = addr.getElementType();
655663
if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
656-
// Boolean vectors use `iN` as storage type.
664+
// Boolean vectors use `iN` as storage type. The type conversion in
665+
// CIRGenTypes::convertType (lines 675-683) returns an integer type for
666+
// ExtVectorBoolType, so eltTy is already an integer. Skip vector
667+
// optimizations for bool vectors since they're not actually vectors in CIR.
657668
if (clangVecTy->isExtVectorBoolType()) {
658-
llvm_unreachable("isExtVectorBoolType NYI");
659-
}
660-
661-
// Handle vectors of size 3 like size 4 for better performance.
662-
const auto vTy = cast<cir::VectorType>(eltTy);
663-
auto newVecTy =
664-
CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts());
669+
// Storage is already an integer type, nothing special needed
670+
} else {
671+
// Handle vectors of size 3 like size 4 for better performance.
672+
const auto vTy = cast<cir::VectorType>(eltTy);
673+
auto newVecTy =
674+
CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts());
665675

666-
if (vTy != newVecTy) {
667-
llvm_unreachable("NYI");
676+
if (vTy != newVecTy) {
677+
llvm_unreachable("NYI");
678+
}
668679
}
669680
}
670681

@@ -2956,6 +2967,13 @@ mlir::Value CIRGenFunction::emitFromMemory(mlir::Value Value, QualType Ty) {
29562967
llvm_unreachable("NIY");
29572968
}
29582969

2970+
// ExtVectorBoolType: In ClangIR, ExtVectorBoolType is always represented
2971+
// as an integer type (!cir.int<u, N>) throughout the IR, including both
2972+
// in registers and in memory. This differs from traditional CodeGen where
2973+
// it may need truncation from storage type to value type. Since we use
2974+
// integer representation consistently, no conversion is needed.
2975+
// See CIRGenTypes.cpp:675-683 for the type conversion logic.
2976+
29592977
return Value;
29602978
}
29612979

@@ -2977,24 +2995,27 @@ mlir::Value CIRGenFunction::emitLoadOfScalar(Address addr, bool isVolatile,
29772995
auto eltTy = addr.getElementType();
29782996

29792997
if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
2980-
// Boolean vectors use `iN` as storage type.
2998+
// Boolean vectors use `iN` as storage type. The type conversion in
2999+
// CIRGenTypes::convertType (lines 675-683) returns an integer type for
3000+
// ExtVectorBoolType, so eltTy is already an integer. Skip vector
3001+
// optimizations for bool vectors since they're not actually vectors in CIR.
29813002
if (clangVecTy->isExtVectorBoolType()) {
2982-
llvm_unreachable("NYI");
2983-
}
2984-
2985-
// Handle vectors of size 3 like size 4 for better performance.
2986-
const auto vTy = cast<cir::VectorType>(eltTy);
2987-
auto newVecTy =
2988-
CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts());
2989-
2990-
if (vTy != newVecTy) {
2991-
const Address cast = addr.withElementType(builder, newVecTy);
2992-
mlir::Value v = builder.createLoad(loc, cast, isVolatile);
2993-
const uint64_t oldNumElements = vTy.getSize();
2994-
SmallVector<int64_t, 16> mask(oldNumElements);
2995-
std::iota(mask.begin(), mask.end(), 0);
2996-
v = builder.createVecShuffle(loc, v, mask);
2997-
return emitFromMemory(v, ty);
3003+
// Storage is already an integer type, nothing special needed
3004+
} else {
3005+
// Handle vectors of size 3 like size 4 for better performance.
3006+
const auto vTy = cast<cir::VectorType>(eltTy);
3007+
auto newVecTy =
3008+
CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts());
3009+
3010+
if (vTy != newVecTy) {
3011+
const Address cast = addr.withElementType(builder, newVecTy);
3012+
mlir::Value v = builder.createLoad(loc, cast, isVolatile);
3013+
const uint64_t oldNumElements = vTy.getSize();
3014+
SmallVector<int64_t, 16> mask(oldNumElements);
3015+
std::iota(mask.begin(), mask.end(), 0);
3016+
v = builder.createVecShuffle(loc, v, mask);
3017+
return emitFromMemory(v, ty);
3018+
}
29983019
}
29993020
}
30003021

clang/lib/CIR/CodeGen/CIRGenExprConst.cpp

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,11 +1134,42 @@ class ConstExprEmitter
11341134
}
11351135

11361136
mlir::Attribute EmitVectorInitialization(InitListExpr *ILE, QualType T) {
1137-
cir::VectorType VecTy = mlir::cast<cir::VectorType>(CGM.convertType(T));
1138-
unsigned NumElements = VecTy.getSize();
1137+
auto *VecTy = T->castAs<VectorType>();
1138+
1139+
// ExtVectorBoolType uses integer storage, not vector type
1140+
if (VecTy->isExtVectorBoolType()) {
1141+
// For ExtVectorBoolType, the storage is an integer type
1142+
// Compute the value by packing bools into an integer
1143+
uint64_t numElements = VecTy->getNumElements();
1144+
unsigned numInits = ILE->getNumInits();
1145+
assert(numElements >= numInits && "Too many initializers for a vector");
1146+
1147+
// Create integer value by packing bool elements
1148+
uint64_t value = 0;
1149+
for (unsigned i = 0; i < numInits; ++i) {
1150+
auto Init = ILE->getInit(i);
1151+
Expr::EvalResult result;
1152+
if (!Init->EvaluateAsRValue(result, CGM.getASTContext()))
1153+
return {};
1154+
bool boolVal = result.Val.getInt().getBoolValue();
1155+
if (boolVal)
1156+
value |= (uint64_t(1) << i);
1157+
}
1158+
1159+
// Pad to at least 8 bits
1160+
uint64_t storageBits = std::max<uint64_t>(numElements, 8);
1161+
auto storageTy =
1162+
cir::IntType::get(CGM.getBuilder().getContext(), storageBits,
1163+
/*isSigned=*/false);
1164+
return cir::IntAttr::get(storageTy, value);
1165+
}
1166+
1167+
// Regular vector type
1168+
cir::VectorType CIRVecTy = mlir::cast<cir::VectorType>(CGM.convertType(T));
1169+
unsigned NumElements = CIRVecTy.getSize();
11391170
unsigned NumInits = ILE->getNumInits();
11401171
assert(NumElements >= NumInits && "Too many initializers for a vector");
1141-
QualType EltTy = T->castAs<VectorType>()->getElementType();
1172+
QualType EltTy = VecTy->getElementType();
11421173
SmallVector<mlir::Attribute, 8> Elts;
11431174
// Process the explicit initializers
11441175
for (unsigned i = 0; i < NumInits; ++i) {
@@ -1149,10 +1180,11 @@ class ConstExprEmitter
11491180
}
11501181
// Zero-fill the rest of the vector
11511182
for (unsigned i = NumInits; i < NumElements; ++i) {
1152-
Elts.push_back(CGM.getBuilder().getZeroInitAttr(VecTy.getElementType()));
1183+
Elts.push_back(
1184+
CGM.getBuilder().getZeroInitAttr(CIRVecTy.getElementType()));
11531185
}
11541186
return cir::ConstVectorAttr::get(
1155-
VecTy, mlir::ArrayAttr::get(CGM.getBuilder().getContext(), Elts));
1187+
CIRVecTy, mlir::ArrayAttr::get(CGM.getBuilder().getContext(), Elts));
11561188
}
11571189

11581190
mlir::Attribute VisitImplicitValueInitExpr(ImplicitValueInitExpr *E,

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,39 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
309309
if (E->getBase()->getType()->isVectorType()) {
310310
assert(!cir::MissingFeatures::scalableVectors() &&
311311
"NYI: index into scalable vector");
312-
// Subscript of vector type. This is handled differently, with a custom
313-
// operation.
312+
313+
// ExtVectorBoolType uses integer storage, handle it specially
314+
const auto *VecTy = E->getBase()
315+
->getType()
316+
.getCanonicalType()
317+
->getAs<clang::VectorType>();
318+
if (VecTy && VecTy->isExtVectorBoolType()) {
319+
// For ExtVectorBoolType, extract a bit from the integer
320+
mlir::Value IntValue = Visit(E->getBase());
321+
mlir::Value IndexValue = Visit(E->getIdx());
322+
323+
// Extract the bit: (IntValue >> IndexValue) & 1
324+
auto Loc = CGF.getLoc(E->getSourceRange());
325+
auto BoolTy = CGF.builder.getBoolTy();
326+
auto IntTy = IntValue.getType();
327+
328+
// Shift right by index: IntValue >> IndexValue
329+
mlir::Value Shifted =
330+
cir::ShiftOp::create(CGF.builder, Loc, IntTy, IntValue, IndexValue,
331+
/*isShiftLeft=*/false);
332+
333+
// Mask with 1: Shifted & 1
334+
mlir::Value One = CGF.builder.getConstInt(Loc, IntTy, 1);
335+
mlir::Value Masked = cir::BinOp::create(
336+
CGF.builder, Loc, IntTy, cir::BinOpKind::And, Shifted, One);
337+
338+
// Convert to bool: Masked != 0
339+
mlir::Value Zero = CGF.builder.getConstInt(Loc, IntTy, 0);
340+
return cir::CmpOp::create(CGF.builder, Loc, BoolTy, cir::CmpOpKind::ne,
341+
Masked, Zero);
342+
}
343+
344+
// Regular vector subscript
314345
mlir::Value VecValue = Visit(E->getBase());
315346
mlir::Value IndexValue = Visit(E->getIdx());
316347
return cir::VecExtractOp::create(CGF.getBuilder(),

clang/lib/CIR/CodeGen/CIRGenTypes.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,18 @@ mlir::Type CIRGenTypes::convertType(QualType T) {
670670
case Type::ExtVector:
671671
case Type::Vector: {
672672
const VectorType *V = cast<VectorType>(Ty);
673-
auto ElementType = convertTypeForMem(V->getElementType());
674-
ResultType = cir::VectorType::get(ElementType, V->getNumElements());
673+
// Boolean vectors use an integer as storage type, matching traditional
674+
// CodeGen. For N bool elements, storage is iM where M = max(N, 8).
675+
if (V->isExtVectorBoolType()) {
676+
uint64_t numElements = V->getNumElements();
677+
// Pad to at least one byte (8 bits)
678+
uint64_t storageBits = std::max<uint64_t>(numElements, 8);
679+
ResultType = cir::IntType::get(Builder.getContext(), storageBits,
680+
/*isSigned=*/false);
681+
} else {
682+
auto ElementType = convertTypeForMem(V->getElementType());
683+
ResultType = cir::VectorType::get(ElementType, V->getNumElements());
684+
}
675685
break;
676686
}
677687
case Type::ConstantMatrix: {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s
3+
4+
// Test basic ext_vector_type with bool elements
5+
typedef bool bool4 __attribute__((ext_vector_type(4)));
6+
typedef bool bool2 __attribute__((ext_vector_type(2)));
7+
typedef bool bool16 __attribute__((ext_vector_type(16)));
8+
9+
// CHECK-LABEL: cir.func {{.*}}@_Z10test_basicv
10+
void test_basic() {
11+
// CHECK: %[[ALLOCA:.*]] = cir.alloca !u8i, !cir.ptr<!u8i>, ["v"
12+
bool4 v = {true, false, true, false};
13+
// CHECK: %[[CONST:.*]] = cir.const #cir.int<5> : !u8i
14+
// CHECK: cir.store {{.*}} %[[CONST]], %[[ALLOCA]]
15+
}
16+
17+
// CHECK-LABEL: cir.func {{.*}}@_Z14test_subscriptv
18+
void test_subscript() {
19+
bool4 v = {true, false, true, false};
20+
// CHECK: %[[LOAD:.*]] = cir.load{{.*}}!u8i
21+
// CHECK: %[[IDX:.*]] = cir.const #cir.int<2>
22+
// CHECK: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD]]{{.*}}, %[[IDX]]
23+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
24+
// CHECK: cir.cmp(ne,{{.*}}){{.*}}!cir.bool
25+
bool b = v[2];
26+
}
27+
28+
// CHECK-LABEL: cir.func {{.*}}@_Z8test_ops
29+
void test_ops(bool4 a, bool4 b) {
30+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
31+
bool4 c = a & b;
32+
}
33+
34+
// NOTE: The following operations are not yet fully implemented for
35+
// ExtVectorBoolType and require special handling:
36+
// - Element assignment (v[2] = true): Requires bit manipulation to set/clear individual bits
37+
// - Unary logical NOT (!v): May require special handling beyond bitwise NOT
38+
39+
// Test bitwise operations
40+
// CHECK-LABEL: cir.func {{.*}}@_Z16test_bitwise_opsv
41+
void test_bitwise_ops() {
42+
bool4 a = {true, false, true, false};
43+
bool4 b = {false, true, true, false};
44+
45+
// Bitwise AND
46+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
47+
bool4 c = a & b;
48+
49+
// Bitwise OR
50+
// CHECK: cir.binop(or,{{.*}}){{.*}}!u8i
51+
bool4 d = a | b;
52+
53+
// Bitwise XOR
54+
// CHECK: cir.binop(xor,{{.*}}){{.*}}!u8i
55+
bool4 e = a ^ b;
56+
}
57+
58+
// Test different vector sizes
59+
// CHECK-LABEL: cir.func {{.*}}@_Z17test_vector_sizesv
60+
void test_vector_sizes() {
61+
// bool2 uses u8i (padded to 8 bits minimum)
62+
// CHECK: cir.alloca !u8i, !cir.ptr<!u8i>, ["v2"
63+
bool2 v2 = {true, false};
64+
// CHECK-DAG: cir.const #cir.int<1> : !u8i
65+
// CHECK-DAG: cir.store{{.*}}!u8i, !cir.ptr<!u8i>
66+
67+
// bool16 uses u16i
68+
// CHECK-DAG: cir.alloca !u16i, !cir.ptr<!u16i>, ["v16"
69+
bool16 v16 = {true, false, true, false, true, false, true, false,
70+
false, true, false, true, false, true, false, true};
71+
// CHECK-DAG: cir.const #cir.int<43605> : !u16i
72+
// CHECK-DAG: cir.store{{.*}}!u16i, !cir.ptr<!u16i>
73+
}
74+
75+
// Test function parameters and returns
76+
// CHECK-LABEL: cir.func {{.*}}@_Z12invert_bool4
77+
// CHECK-SAME: %arg0: !u8i
78+
// CHECK-SAME: -> !u8i
79+
bool4 invert_bool4(bool4 v) {
80+
// Bitwise NOT
81+
// CHECK: %[[LOAD:.*]] = cir.load{{.*}}!u8i
82+
// CHECK: cir.unary(not, %[[LOAD]]){{.*}}!u8i
83+
return ~v;
84+
}
85+
86+
// Test all bits set and all bits clear
87+
// CHECK-LABEL: cir.func {{.*}}@_Z15test_edge_casesv
88+
void test_edge_cases() {
89+
// All false (0)
90+
// CHECK-DAG: cir.alloca !u8i, !cir.ptr<!u8i>, ["all_false"
91+
bool4 all_false = {false, false, false, false};
92+
// CHECK-DAG: cir.const #cir.int<0> : !u8i
93+
// CHECK-DAG: cir.store{{.*}}!u8i, !cir.ptr<!u8i>
94+
95+
// All true (15 = 0b1111 for 4 bits)
96+
// CHECK-DAG: cir.alloca !u8i, !cir.ptr<!u8i>, ["all_true"
97+
bool4 all_true = {true, true, true, true};
98+
// CHECK-DAG: cir.const #cir.int<15> : !u8i
99+
// CHECK-DAG: cir.store{{.*}}!u8i, !cir.ptr<!u8i>
100+
}
101+
102+
// Test subscript with variable index
103+
// CHECK-LABEL: cir.func {{.*}}@_Z23test_variable_subscript
104+
void test_variable_subscript(int idx) {
105+
bool4 v = {true, false, true, false};
106+
// CHECK: cir.load{{.*}}!u8i
107+
// CHECK: cir.load{{.*}}!s32i
108+
// CHECK: cir.shift(right,{{.*}}){{.*}}!u8i
109+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
110+
bool b = v[idx];
111+
}
112+
113+
// Test initialization with all same value
114+
// CHECK-LABEL: cir.func {{.*}}@_Z18test_same_init_valv
115+
void test_same_init_val() {
116+
// Initialize all elements to true using splat
117+
// CHECK: cir.alloca !u8i, !cir.ptr<!u8i>, ["v"
118+
bool4 v = {true, true, true, true};
119+
// CHECK: cir.const #cir.int<15> : !u8i
120+
// CHECK: cir.store{{.*}}!u8i, !cir.ptr<!u8i>
121+
}
122+
123+
// Test multiple operations in sequence
124+
// CHECK-LABEL: cir.func {{.*}}@_Z17test_multiple_opsv
125+
void test_multiple_ops() {
126+
bool4 a = {true, false, true, false};
127+
bool4 b = {false, true, true, false};
128+
129+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
130+
bool4 c = a & b;
131+
// CHECK: cir.binop(or,{{.*}}){{.*}}!u8i
132+
bool4 d = c | b;
133+
// CHECK: cir.unary(not,{{.*}}){{.*}}!u8i
134+
bool4 e = ~d;
135+
}
136+
137+
// Test reading specific elements
138+
// CHECK-LABEL: cir.func {{.*}}@_Z18test_read_elementsv
139+
void test_read_elements() {
140+
bool4 v = {true, false, true, false};
141+
142+
// Read element 0
143+
// CHECK: cir.load{{.*}}!u8i
144+
// CHECK: cir.const #cir.int<0>
145+
// CHECK: cir.shift(right,{{.*}}){{.*}}!u8i
146+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
147+
bool e0 = v[0];
148+
149+
// Read element 3
150+
// CHECK: cir.load{{.*}}!u8i
151+
// CHECK: cir.const #cir.int<3>
152+
// CHECK: cir.shift(right,{{.*}}){{.*}}!u8i
153+
// CHECK: cir.binop(and,{{.*}}){{.*}}!u8i
154+
bool e3 = v[3];
155+
}

0 commit comments

Comments
 (0)