Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
51 changes: 43 additions & 8 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -902,22 +902,57 @@ void CIRGenFunction::emitStoreThroughLValue(RValue Src, LValue Dst,
bool isInit) {
if (!Dst.isSimple()) {
if (Dst.isVectorElt()) {
mlir::Location loc = Dst.getVectorPointer().getLoc();
mlir::Value vector = builder.createLoad(loc, Dst.getVectorAddress());
mlir::Value srcVal = Src.getScalarVal();

// Check if this is an ExtVectorBoolType element assignment
QualType vectorType = Dst.getType();
if (const auto *vecTy = vectorType->getAs<clang::VectorType>()) {
if (vecTy->isExtVectorBoolType()) {
llvm_unreachable(
"NYI: ExtVectorBoolType element assignment (requires bit "
"manipulation to set/clear individual bits in integer storage)");
// ExtVectorBoolType is stored as an integer (!cir.int<u, N>) in CIR.
// To set an element, we need to:
// 1. Bitcast iN -> <N x !cir.int<u, 1>> where N is the STORAGE size
// (padded to at least 8)
// 2. Insert element at the actual index (0 to numElements-1)
// 3. Bitcast <N x !cir.int<u, 1>> -> iN
// The padding bits (numElements to N-1) are preserved through the
// operation.

uint64_t numElements = vecTy->getNumElements();
// Storage is padded to at least 8 bits (1 byte)
uint64_t storageBits = std::max<uint64_t>(numElements, 8);

// Use !cir.int<u, 1> instead of !cir.bool for vector elements
auto i1Ty = cir::IntType::get(builder.getContext(), 1, false);
// Create vector with storage size (padded), not actual element count
auto vecI1Ty = cir::VectorType::get(i1Ty, storageBits);

// Bitcast integer storage to vector of i1 (with padding)
vector = builder.createBitcast(loc, vector, vecI1Ty);

// Insert the element (cast bool to i1 if needed)
if (srcVal.getType() != i1Ty) {
srcVal = cir::CastOp::create(builder, loc, i1Ty,
cir::CastKind::bool_to_int, srcVal);
}
// Insert at the actual index - padding bits remain unchanged
vector = cir::VecInsertOp::create(builder, loc, vector, srcVal,
Dst.getVectorIdx());

// Bitcast back to integer storage
vector = builder.createBitcast(
loc, vector, Dst.getVectorAddress().getElementType());

builder.createStore(loc, vector, Dst.getVectorAddress());
return;
}
}

// Read/modify/write the vector, inserting the new element
mlir::Location loc = Dst.getVectorPointer().getLoc();
mlir::Value Vector = builder.createLoad(loc, Dst.getVectorAddress());
Vector = cir::VecInsertOp::create(builder, loc, Vector,
Src.getScalarVal(), Dst.getVectorIdx());
builder.createStore(loc, Vector, Dst.getVectorAddress());
vector = cir::VecInsertOp::create(builder, loc, vector, srcVal,
Dst.getVectorIdx());
builder.createStore(loc, vector, Dst.getVectorAddress());
return;
}

Expand Down
118 changes: 107 additions & 11 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,16 +1003,67 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
Builder.createCompare(CGF.getLoc(E->getExprLoc()), kind, lhs, rhs);
} else if (!LHSTy->isAnyComplexType() && !RHSTy->isAnyComplexType()) {
BinOpInfo BOInfo = emitBinOps(E);
mlir::Value LHS = BOInfo.LHS;
mlir::Value RHS = BOInfo.RHS;
mlir::Value lhs = BOInfo.LHS;
mlir::Value rhs = BOInfo.RHS;

if (LHSTy->isVectorType()) {
// Check for ExtVectorBoolType which uses integer storage, not vector
if (const auto *vecTy = LHSTy->getAs<clang::VectorType>()) {
if (vecTy->isExtVectorBoolType()) {
llvm_unreachable(
"NYI: ExtVectorBoolType comparison operations (requires "
"element-wise comparison on packed integer representation)");
// ExtVectorBoolType is stored as an integer (!cir.int<u, N>) in
// CIR. To compare elements, we need to:
// 1. Bitcast iN -> <P x !cir.int<u, 1>> where P is the STORAGE size
// (padded to at least 8)
// 2. Shuffle to extract actual elements <P x i1> -> <N x i1>
// 3. Perform vector comparison on actual elements
// 4. Shuffle result back <N x i1> -> <P x i1> (pad with undef)
// 5. Bitcast result back to iN

uint64_t numElements = vecTy->getNumElements();
// Storage is padded to at least 8 bits (1 byte)
uint64_t storageBits = std::max<uint64_t>(numElements, 8);

// Use !cir.int<u, 1> instead of !cir.bool for vector elements
auto i1Ty = cir::IntType::get(Builder.getContext(), 1, false);
// Create vector types: padded storage size and actual element count
auto paddedVecTy = cir::VectorType::get(i1Ty, storageBits);
auto actualVecTy = cir::VectorType::get(i1Ty, numElements);

// Bitcast integer storage to padded vector of i1 for both operands
lhs =
Builder.createBitcast(CGF.getLoc(BOInfo.Loc), lhs, paddedVecTy);
rhs =
Builder.createBitcast(CGF.getLoc(BOInfo.Loc), rhs, paddedVecTy);

// Extract actual elements using shuffle (indices 0 to
// numElements-1)
llvm::SmallVector<int64_t> extractIndices(numElements);
for (uint64_t i = 0; i < numElements; ++i)
extractIndices[i] = i;
lhs = Builder.createVecShuffle(CGF.getLoc(BOInfo.Loc), lhs, lhs,
extractIndices);
rhs = Builder.createVecShuffle(CGF.getLoc(BOInfo.Loc), rhs, rhs,
extractIndices);

// Perform element-wise comparison on actual elements
cir::CmpOpKind kind = ClangCmpToCIRCmp(E->getOpcode());
Result = cir::VecCmpOp::create(Builder, CGF.getLoc(BOInfo.Loc),
actualVecTy, kind, lhs, rhs);

// Pad result back to storage size using shuffle
llvm::SmallVector<int64_t> padIndices(storageBits);
for (uint64_t i = 0; i < numElements; ++i)
padIndices[i] = i;
for (uint64_t i = numElements; i < storageBits; ++i)
padIndices[i] = -1; // undef for padding bits
Result = Builder.createVecShuffle(CGF.getLoc(BOInfo.Loc), Result,
Result, padIndices);

// Bitcast back to integer storage
Result = Builder.createBitcast(CGF.getLoc(BOInfo.Loc), Result,
CGF.convertType(E->getType()));
return emitScalarConversion(Result, CGF.getContext().BoolTy,
E->getType(), E->getExprLoc());
}
}

Expand All @@ -1036,13 +1087,13 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {

// Unsigned integers and pointers.
if (CGF.CGM.getCodeGenOpts().StrictVTablePointers &&
mlir::isa<cir::PointerType>(LHS.getType()) &&
mlir::isa<cir::PointerType>(RHS.getType())) {
mlir::isa<cir::PointerType>(lhs.getType()) &&
mlir::isa<cir::PointerType>(rhs.getType())) {
llvm_unreachable("NYI");
}

cir::CmpOpKind Kind = ClangCmpToCIRCmp(E->getOpcode());
Result = Builder.createCompare(CGF.getLoc(BOInfo.Loc), Kind, LHS, RHS);
Result = Builder.createCompare(CGF.getLoc(BOInfo.Loc), Kind, lhs, rhs);
}
} else { // Complex Comparison: can only be an equality comparison.
assert(0 && "not implemented");
Expand Down Expand Up @@ -2119,9 +2170,54 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *E) {
// Check for ExtVectorBoolType which uses integer storage, not vector
if (const auto *vecTy = E->getType()->getAs<clang::VectorType>()) {
if (vecTy->isExtVectorBoolType()) {
llvm_unreachable(
"NYI: ExtVectorBoolType logical NOT (requires handling padding "
"bits in integer storage to ensure correct element-wise negation)");
// ExtVectorBoolType is stored as an integer (!cir.int<u, N>) in CIR.
// Logical NOT is implemented as comparison with zero: !v == (v == 0)
// 1. Get the operand (already in integer form)
// 2. Bitcast iN -> <P x !cir.int<u, 1>> where P is the STORAGE size
// (padded to at least 8)
// 3. Shuffle to extract actual elements <P x i1> -> <N x i1>
// 4. Compare with zero vector
// 5. Shuffle result back <N x i1> -> <P x i1> (pad with undef)
// 6. Bitcast result back to iN

mlir::Value oper = Visit(E->getSubExpr());
mlir::Location loc = CGF.getLoc(E->getExprLoc());

uint64_t numElements = vecTy->getNumElements();
// Storage is padded to at least 8 bits (1 byte)
uint64_t storageBits = std::max<uint64_t>(numElements, 8);

// Use !cir.int<u, 1> instead of !cir.bool for vector elements
auto i1Ty = cir::IntType::get(Builder.getContext(), 1, false);
// Create vector types: padded storage size and actual element count
auto paddedVecTy = cir::VectorType::get(i1Ty, storageBits);
auto actualVecTy = cir::VectorType::get(i1Ty, numElements);

// Bitcast integer storage to padded vector of i1
oper = Builder.createBitcast(loc, oper, paddedVecTy);

// Extract actual elements using shuffle (indices 0 to numElements-1)
llvm::SmallVector<int64_t> extractIndices(numElements);
for (uint64_t i = 0; i < numElements; ++i)
extractIndices[i] = i;
oper = Builder.createVecShuffle(loc, oper, oper, extractIndices);

// Create zero vector and compare with actual elements
mlir::Value zeroVec = Builder.getNullValue(actualVecTy, loc);
mlir::Value result = cir::VecCmpOp::create(
Builder, loc, actualVecTy, cir::CmpOpKind::eq, oper, zeroVec);

// Pad result back to storage size using shuffle
llvm::SmallVector<int64_t> padIndices(storageBits);
for (uint64_t i = 0; i < numElements; ++i)
padIndices[i] = i;
for (uint64_t i = numElements; i < storageBits; ++i)
padIndices[i] = -1; // undef for padding bits
result = Builder.createVecShuffle(loc, result, result, padIndices);

// Bitcast back to integer storage
return Builder.createBitcast(loc, result,
CGF.convertType(E->getType()));
}
}

Expand Down
131 changes: 130 additions & 1 deletion clang/test/CIR/CodeGen/extvector-bool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@
// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.og.ll
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.og.ll -DDISABLE_ELEMENT_ASSIGN_TEST
// RUN: FileCheck --input-file=%t.og.ll %s --check-prefix=OGCG
//
// NOTE: Element assignment test (test_element_assign) is excluded from OGCG
// testing due to a bug in classic CodeGen (clang/lib/CodeGen/CGExpr.cpp:2585-2587).
// Classic CodeGen calls VecTy->getScalarType() on an IntegerType before bitcasting
// to VectorType for ExtVectorBoolType, causing assertion failure. CIR correctly
// performs the bitcast first. This is a justifiable divergence fixing a bug.
// Bug verified: classic CodeGen crashes with assertion when compiling element assignment.
//
// The OGCG tests below verify that CIR's LLVM lowering for comparisons and logical
// NOT matches classic CodeGen's output, demonstrating consistency where classic
// CodeGen works correctly.

// Test basic ext_vector_type with bool elements
typedef bool bool4 __attribute__((ext_vector_type(4)));
Expand Down Expand Up @@ -204,3 +215,121 @@ void test_read_elements() {
// CIR: cir.binop(and,{{.*}}){{.*}}!u8i
bool e3 = v[3];
}

#ifndef DISABLE_ELEMENT_ASSIGN_TEST
// Test element assignment (v[2] = true)
// NOTE: This test is disabled for classic CodeGen due to a bug in CGExpr.cpp:2585-2587
// where VecTy->getScalarType() is called on an integer type before bitcasting to vector.
// CIR-LABEL: cir.func {{.*}}@_Z{{.*}}test_element_assignv
void test_element_assign() {
bool4 v = {true, false, true, false};
// CIR: cir.load{{.*}}!u8i
// CIR: cir.cast bitcast{{.*}}!u8i -> !cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.cast bool_to_int{{.*}}!cir.bool -> !cir.int<u, 1>
// CIR: cir.vec.insert
// CIR: cir.cast bitcast{{.*}}!cir.vector<!cir.int<u, 1> x 8> -> !u8i
// CIR: cir.store{{.*}}!u8i, !cir.ptr<!u8i>
v[2] = true;

// LLVM-LABEL: define {{.*}}@_Z{{.*}}test_element_assignv
// LLVM: %[[VEC_LOAD:.*]] = load i8
// LLVM: %[[VEC_BITCAST:.*]] = bitcast i8 %[[VEC_LOAD]] to <8 x i1>
// LLVM: %[[VEC_INSERT:.*]] = insertelement <8 x i1> %[[VEC_BITCAST]], i1 true, i32 2
// LLVM: %[[VEC_BITCAST_BACK:.*]] = bitcast <8 x i1> %[[VEC_INSERT]] to i8
// LLVM: store i8 %[[VEC_BITCAST_BACK]]
}
#endif

// Test comparison operations (a == b, a != b)
// CIR-LABEL: cir.func {{.*}}@_Z{{.*}}test_comparisonv
void test_comparison() {
bool4 a = {true, false, true, false};
bool4 b = {false, true, true, false};

// Test equality
// CIR: cir.load{{.*}}!u8i
// CIR: cir.load{{.*}}!u8i
// CIR: cir.cast bitcast{{.*}}!u8i -> !cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.cast bitcast{{.*}}!u8i -> !cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 8>{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 8>{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.cmp(eq,{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 4>{{.*}}!cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.cast bitcast{{.*}}!cir.vector<!cir.int<u, 1> x 8> -> !u8i
bool4 c = a == b;

// Test inequality
// CIR: cir.load{{.*}}!u8i
// CIR: cir.load{{.*}}!u8i
// CIR: cir.cast bitcast{{.*}}!u8i -> !cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.cast bitcast{{.*}}!u8i -> !cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 8>{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 8>{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.cmp(ne,{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 4>{{.*}}!cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.cast bitcast{{.*}}!cir.vector<!cir.int<u, 1> x 8> -> !u8i
bool4 d = a != b;

// LLVM-LABEL: define {{.*}}@_Z{{.*}}test_comparisonv
// LLVM: %[[A_LOAD:.*]] = load i8
// LLVM: %[[B_LOAD:.*]] = load i8
// LLVM: %[[A_BITCAST:.*]] = bitcast i8 %[[A_LOAD]] to <8 x i1>
// LLVM: %[[B_BITCAST:.*]] = bitcast i8 %[[B_LOAD]] to <8 x i1>
// LLVM: %[[A_EXTRACT:.*]] = shufflevector <8 x i1> %[[A_BITCAST]], <8 x i1> %[[A_BITCAST]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// LLVM: %[[B_EXTRACT:.*]] = shufflevector <8 x i1> %[[B_BITCAST]], <8 x i1> %[[B_BITCAST]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// LLVM: %[[VEC_CMP:.*]] = icmp eq <4 x i1> %[[A_EXTRACT]], %[[B_EXTRACT]]
// LLVM: %[[RESULT_PAD:.*]] = shufflevector <4 x i1> %[[VEC_CMP]], <4 x i1> %[[VEC_CMP]]
// LLVM: %[[RESULT_BITCAST:.*]] = bitcast <8 x i1> %[[RESULT_PAD]] to i8
// LLVM: store i8 %[[RESULT_BITCAST]]
// LLVM: %[[A_LOAD2:.*]] = load i8
// LLVM: %[[B_LOAD2:.*]] = load i8
// LLVM: %[[A_BITCAST2:.*]] = bitcast i8 %[[A_LOAD2]] to <8 x i1>
// LLVM: %[[B_BITCAST2:.*]] = bitcast i8 %[[B_LOAD2]] to <8 x i1>
// LLVM: %[[A_EXTRACT2:.*]] = shufflevector <8 x i1> %[[A_BITCAST2]], <8 x i1> %[[A_BITCAST2]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// LLVM: %[[B_EXTRACT2:.*]] = shufflevector <8 x i1> %[[B_BITCAST2]], <8 x i1> %[[B_BITCAST2]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// LLVM: %[[VEC_CMP2:.*]] = icmp ne <4 x i1> %[[A_EXTRACT2]], %[[B_EXTRACT2]]
// LLVM: %[[RESULT_PAD2:.*]] = shufflevector <4 x i1> %[[VEC_CMP2]], <4 x i1> %[[VEC_CMP2]]
// LLVM: %[[RESULT_BITCAST2:.*]] = bitcast <8 x i1> %[[RESULT_PAD2]] to i8
// LLVM: store i8 %[[RESULT_BITCAST2]]

// OGCG-LABEL: define {{.*}}@_Z{{.*}}test_comparisonv
// OGCG: bitcast i8 {{.*}} to <8 x i1>
// OGCG: shufflevector <8 x i1> {{.*}}, <8 x i1> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// OGCG: bitcast i8 {{.*}} to <8 x i1>
// OGCG: shufflevector <8 x i1> {{.*}}, <8 x i1> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// OGCG: icmp eq <4 x i1>
// OGCG: shufflevector <4 x i1> {{.*}}, <4 x i1> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison>
// OGCG: bitcast <8 x i1> {{.*}} to i8
// OGCG: icmp ne <4 x i1>
}

// Test logical NOT (!v)
// CIR-LABEL: cir.func {{.*}}@_Z{{.*}}test_logical_notv
void test_logical_not() {
bool4 v = {true, false, true, false};

// CIR: cir.load{{.*}}!u8i
// CIR: cir.cast bitcast{{.*}}!u8i -> !cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 8>{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.const #cir.zero : !cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.cmp(eq,{{.*}}!cir.vector<!cir.int<u, 1> x 4>
// CIR: cir.vec.shuffle{{.*}}!cir.vector<!cir.int<u, 1> x 4>{{.*}}!cir.vector<!cir.int<u, 1> x 8>
// CIR: cir.cast bitcast{{.*}}!cir.vector<!cir.int<u, 1> x 8> -> !u8i
bool4 n = !v;

// LLVM-LABEL: define {{.*}}@_Z{{.*}}test_logical_notv
// LLVM: %[[VEC_LOAD:.*]] = load i8
// LLVM: %[[VEC_BITCAST:.*]] = bitcast i8 %[[VEC_LOAD]] to <8 x i1>
// LLVM: %[[VEC_EXTRACT:.*]] = shufflevector <8 x i1> %[[VEC_BITCAST]], <8 x i1> %[[VEC_BITCAST]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// LLVM: icmp eq <4 x i1> %[[VEC_EXTRACT]], zeroinitializer
// LLVM: shufflevector <4 x i1>
// LLVM: bitcast <8 x i1> %{{.*}} to i8
// LLVM: store i8 %{{.*}}

// OGCG-LABEL: define {{.*}}@_Z{{.*}}test_logical_notv
// OGCG: bitcast i8 {{.*}} to <8 x i1>
// OGCG: shufflevector <8 x i1> {{.*}}, <8 x i1> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
// OGCG: icmp eq <4 x i1> {{.*}}, zeroinitializer
// OGCG: shufflevector <4 x i1> {{.*}}, <4 x i1> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison>
// OGCG: bitcast <8 x i1> {{.*}} to i8
}