diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index adfec6387ca4..4d8553c89ad4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -634,6 +634,14 @@ CIRGenCallee CIRGenFunction::emitCallee(const clang::Expr *E) { mlir::Value CIRGenFunction::emitToMemory(mlir::Value Value, QualType Ty) { // Bool has a different representation in memory than in registers. + + // ExtVectorBoolType: In ClangIR, ExtVectorBoolType is always represented + // as an integer type (!cir.int) throughout the IR, including both + // in registers and in memory. This differs from traditional CodeGen where + // it may exist as a vector type that needs conversion to integer for storage. + // Since we use integer representation consistently, no conversion is needed. + // See CIRGenTypes.cpp:675-683 for the type conversion logic. + return Value; } @@ -653,18 +661,21 @@ void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr, auto eltTy = addr.getElementType(); if (const auto *clangVecTy = ty->getAs()) { - // Boolean vectors use `iN` as storage type. + // Boolean vectors use `iN` as storage type. The type conversion in + // CIRGenTypes::convertType (lines 675-683) returns an integer type for + // ExtVectorBoolType, so eltTy is already an integer. Skip vector + // optimizations for bool vectors since they're not actually vectors in CIR. if (clangVecTy->isExtVectorBoolType()) { - llvm_unreachable("isExtVectorBoolType NYI"); - } - - // Handle vectors of size 3 like size 4 for better performance. - const auto vTy = cast(eltTy); - auto newVecTy = - CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts()); + // Storage is already an integer type, nothing special needed + } else { + // Handle vectors of size 3 like size 4 for better performance. + const auto vTy = cast(eltTy); + auto newVecTy = + CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts()); - if (vTy != newVecTy) { - llvm_unreachable("NYI"); + if (vTy != newVecTy) { + llvm_unreachable("NYI"); + } } } @@ -868,6 +879,16 @@ void CIRGenFunction::emitStoreThroughLValue(RValue Src, LValue Dst, bool isInit) { if (!Dst.isSimple()) { if (Dst.isVectorElt()) { + // Check if this is an ExtVectorBoolType element assignment + QualType vectorType = Dst.getType(); + if (const auto *vecTy = vectorType->getAs()) { + if (vecTy->isExtVectorBoolType()) { + llvm_unreachable( + "NYI: ExtVectorBoolType element assignment (requires bit " + "manipulation to set/clear individual bits in integer storage)"); + } + } + // Read/modify/write the vector, inserting the new element mlir::Location loc = Dst.getVectorPointer().getLoc(); mlir::Value Vector = builder.createLoad(loc, Dst.getVectorAddress()); @@ -2735,6 +2756,8 @@ LValue CIRGenFunction::emitLValue(const Expr *E) { return emitStmtExprLValue(cast(E)); case Expr::ChooseExprClass: return emitLValue(cast(E)->getChosenSubExpr()); + case Expr::CXXTypeidExprClass: + return emitCXXTypeidLValue(cast(E)); } llvm_unreachable("NYI"); @@ -2956,6 +2979,13 @@ mlir::Value CIRGenFunction::emitFromMemory(mlir::Value Value, QualType Ty) { llvm_unreachable("NIY"); } + // ExtVectorBoolType: In ClangIR, ExtVectorBoolType is always represented + // as an integer type (!cir.int) throughout the IR, including both + // in registers and in memory. This differs from traditional CodeGen where + // it may need truncation from storage type to value type. Since we use + // integer representation consistently, no conversion is needed. + // See CIRGenTypes.cpp:675-683 for the type conversion logic. + return Value; } @@ -2977,24 +3007,27 @@ mlir::Value CIRGenFunction::emitLoadOfScalar(Address addr, bool isVolatile, auto eltTy = addr.getElementType(); if (const auto *clangVecTy = ty->getAs()) { - // Boolean vectors use `iN` as storage type. + // Boolean vectors use `iN` as storage type. The type conversion in + // CIRGenTypes::convertType (lines 675-683) returns an integer type for + // ExtVectorBoolType, so eltTy is already an integer. Skip vector + // optimizations for bool vectors since they're not actually vectors in CIR. if (clangVecTy->isExtVectorBoolType()) { - llvm_unreachable("NYI"); - } - - // Handle vectors of size 3 like size 4 for better performance. - const auto vTy = cast(eltTy); - auto newVecTy = - CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts()); - - if (vTy != newVecTy) { - const Address cast = addr.withElementType(builder, newVecTy); - mlir::Value v = builder.createLoad(loc, cast, isVolatile); - const uint64_t oldNumElements = vTy.getSize(); - SmallVector mask(oldNumElements); - std::iota(mask.begin(), mask.end(), 0); - v = builder.createVecShuffle(loc, v, mask); - return emitFromMemory(v, ty); + // Storage is already an integer type, nothing special needed + } else { + // Handle vectors of size 3 like size 4 for better performance. + const auto vTy = cast(eltTy); + auto newVecTy = + CGM.getABIInfo().getOptimalVectorMemoryType(vTy, getLangOpts()); + + if (vTy != newVecTy) { + const Address cast = addr.withElementType(builder, newVecTy); + mlir::Value v = builder.createLoad(loc, cast, isVolatile); + const uint64_t oldNumElements = vTy.getSize(); + SmallVector mask(oldNumElements); + std::iota(mask.begin(), mask.end(), 0); + v = builder.createVecShuffle(loc, v, mask); + return emitFromMemory(v, ty); + } } } @@ -3445,3 +3478,22 @@ RValue CIRGenFunction::emitPseudoObjectRValue(const PseudoObjectExpr *expr, LValue CIRGenFunction::emitPseudoObjectLValue(const PseudoObjectExpr *expr) { return emitPseudoObjectExpr(*this, expr, true, AggValueSlot::ignored()).lv; } + +LValue CIRGenFunction::emitCXXTypeidLValue(const CXXTypeidExpr *E) { + // Emit the typeid expression, which returns a pointer to the RTTI descriptor. + mlir::Value typeInfoPtr = emitCXXTypeidExpr(E); + + // Cast the pointer to the actual type_info type for proper type safety. + auto typeInfoTy = convertTypeForMem(E->getType()); + auto typeInfoPtrTy = builder.getPointerTo(typeInfoTy); + typeInfoPtr = builder.createBitcast(getLoc(E->getSourceRange()), typeInfoPtr, + typeInfoPtrTy); + + // Create an LValue from the pointer with natural alignment. + // We use getTypeAlignInChars() which returns the natural alignment for the + // type_info type, matching traditional CodeGen's getNaturalTypeAlignment(). + Address addr(typeInfoPtr, typeInfoTy, + getContext().getTypeAlignInChars(E->getType())); + + return makeAddrLValue(addr, E->getType(), AlignmentSource::Decl); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp index 0e5a403968f2..d725aa3c5c7a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp @@ -1134,11 +1134,42 @@ class ConstExprEmitter } mlir::Attribute EmitVectorInitialization(InitListExpr *ILE, QualType T) { - cir::VectorType VecTy = mlir::cast(CGM.convertType(T)); - unsigned NumElements = VecTy.getSize(); + auto *VecTy = T->castAs(); + + // ExtVectorBoolType uses integer storage, not vector type + if (VecTy->isExtVectorBoolType()) { + // For ExtVectorBoolType, the storage is an integer type + // Compute the value by packing bools into an integer + uint64_t numElements = VecTy->getNumElements(); + unsigned numInits = ILE->getNumInits(); + assert(numElements >= numInits && "Too many initializers for a vector"); + + // Create integer value by packing bool elements + uint64_t value = 0; + for (unsigned i = 0; i < numInits; ++i) { + auto Init = ILE->getInit(i); + Expr::EvalResult result; + if (!Init->EvaluateAsRValue(result, CGM.getASTContext())) + return {}; + bool boolVal = result.Val.getInt().getBoolValue(); + if (boolVal) + value |= (uint64_t(1) << i); + } + + // Pad to at least 8 bits + uint64_t storageBits = std::max(numElements, 8); + auto storageTy = + cir::IntType::get(CGM.getBuilder().getContext(), storageBits, + /*isSigned=*/false); + return cir::IntAttr::get(storageTy, value); + } + + // Regular vector type + cir::VectorType CIRVecTy = mlir::cast(CGM.convertType(T)); + unsigned NumElements = CIRVecTy.getSize(); unsigned NumInits = ILE->getNumInits(); assert(NumElements >= NumInits && "Too many initializers for a vector"); - QualType EltTy = T->castAs()->getElementType(); + QualType EltTy = VecTy->getElementType(); SmallVector Elts; // Process the explicit initializers for (unsigned i = 0; i < NumInits; ++i) { @@ -1149,10 +1180,11 @@ class ConstExprEmitter } // Zero-fill the rest of the vector for (unsigned i = NumInits; i < NumElements; ++i) { - Elts.push_back(CGM.getBuilder().getZeroInitAttr(VecTy.getElementType())); + Elts.push_back( + CGM.getBuilder().getZeroInitAttr(CIRVecTy.getElementType())); } return cir::ConstVectorAttr::get( - VecTy, mlir::ArrayAttr::get(CGM.getBuilder().getContext(), Elts)); + CIRVecTy, mlir::ArrayAttr::get(CGM.getBuilder().getContext(), Elts)); } mlir::Attribute VisitImplicitValueInitExpr(ImplicitValueInitExpr *E, diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index a1adea6cd172..53deeb2c40ab 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -309,8 +309,39 @@ class ScalarExprEmitter : public StmtVisitor { if (E->getBase()->getType()->isVectorType()) { assert(!cir::MissingFeatures::scalableVectors() && "NYI: index into scalable vector"); - // Subscript of vector type. This is handled differently, with a custom - // operation. + + // ExtVectorBoolType uses integer storage, handle it specially + const auto *VecTy = E->getBase() + ->getType() + .getCanonicalType() + ->getAs(); + if (VecTy && VecTy->isExtVectorBoolType()) { + // For ExtVectorBoolType, extract a bit from the integer + mlir::Value IntValue = Visit(E->getBase()); + mlir::Value IndexValue = Visit(E->getIdx()); + + // Extract the bit: (IntValue >> IndexValue) & 1 + auto Loc = CGF.getLoc(E->getSourceRange()); + auto BoolTy = CGF.builder.getBoolTy(); + auto IntTy = IntValue.getType(); + + // Shift right by index: IntValue >> IndexValue + mlir::Value Shifted = + cir::ShiftOp::create(CGF.builder, Loc, IntTy, IntValue, IndexValue, + /*isShiftLeft=*/false); + + // Mask with 1: Shifted & 1 + mlir::Value One = CGF.builder.getConstInt(Loc, IntTy, 1); + mlir::Value Masked = cir::BinOp::create( + CGF.builder, Loc, IntTy, cir::BinOpKind::And, Shifted, One); + + // Convert to bool: Masked != 0 + mlir::Value Zero = CGF.builder.getConstInt(Loc, IntTy, 0); + return cir::CmpOp::create(CGF.builder, Loc, BoolTy, cir::CmpOpKind::ne, + Masked, Zero); + } + + // Regular vector subscript mlir::Value VecValue = Visit(E->getBase()); mlir::Value IndexValue = Visit(E->getIdx()); return cir::VecExtractOp::create(CGF.getBuilder(), @@ -976,6 +1007,15 @@ class ScalarExprEmitter : public StmtVisitor { mlir::Value RHS = BOInfo.RHS; if (LHSTy->isVectorType()) { + // Check for ExtVectorBoolType which uses integer storage, not vector + if (const auto *vecTy = LHSTy->getAs()) { + if (vecTy->isExtVectorBoolType()) { + llvm_unreachable( + "NYI: ExtVectorBoolType comparison operations (requires " + "element-wise comparison on packed integer representation)"); + } + } + if (!E->getType()->isVectorType()) { // If AltiVec, the comparison results in a numeric type, so we use // intrinsics comparing vectors and giving 0 or 1 as a result @@ -2076,6 +2116,15 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *E) { if (E->getType()->isVectorType() && E->getType()->castAs()->getVectorKind() == VectorKind::Generic) { + // Check for ExtVectorBoolType which uses integer storage, not vector + if (const auto *vecTy = E->getType()->getAs()) { + if (vecTy->isExtVectorBoolType()) { + llvm_unreachable( + "NYI: ExtVectorBoolType logical NOT (requires handling padding " + "bits in integer storage to ensure correct element-wise negation)"); + } + } + mlir::Value oper = Visit(E->getSubExpr()); mlir::Location loc = CGF.getLoc(E->getExprLoc()); auto operVecTy = mlir::cast(oper.getType()); diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp index 4de89fc7081a..72063b913ea6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp @@ -670,8 +670,18 @@ mlir::Type CIRGenTypes::convertType(QualType T) { case Type::ExtVector: case Type::Vector: { const VectorType *V = cast(Ty); - auto ElementType = convertTypeForMem(V->getElementType()); - ResultType = cir::VectorType::get(ElementType, V->getNumElements()); + // Boolean vectors use an integer as storage type, matching traditional + // CodeGen. For N bool elements, storage is iM where M = max(N, 8). + if (V->isExtVectorBoolType()) { + uint64_t numElements = V->getNumElements(); + // Pad to at least one byte (8 bits) + uint64_t storageBits = std::max(numElements, 8); + ResultType = cir::IntType::get(Builder.getContext(), storageBits, + /*isSigned=*/false); + } else { + auto ElementType = convertTypeForMem(V->getElementType()); + ResultType = cir::VectorType::get(ElementType, V->getNumElements()); + } break; } case Type::ConstantMatrix: { diff --git a/clang/test/CIR/CodeGen/extvector-bool.cpp b/clang/test/CIR/CodeGen/extvector-bool.cpp new file mode 100644 index 000000000000..04a2a8c20495 --- /dev/null +++ b/clang/test/CIR/CodeGen/extvector-bool.cpp @@ -0,0 +1,206 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// 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: FileCheck --input-file=%t.og.ll %s --check-prefix=OGCG + +// Test basic ext_vector_type with bool elements +typedef bool bool4 __attribute__((ext_vector_type(4))); +typedef bool bool2 __attribute__((ext_vector_type(2))); +typedef bool bool16 __attribute__((ext_vector_type(16))); + +// CIR-LABEL: cir.func {{.*}}@_Z10test_basicv +void test_basic() { + // CIR: %[[ALLOCA:.*]] = cir.alloca !u8i, !cir.ptr, ["v" + bool4 v = {true, false, true, false}; + // CIR: %[[CONST:.*]] = cir.const #cir.int<5> : !u8i + // CIR: cir.store {{.*}} %[[CONST]], %[[ALLOCA]] + + // LLVM-LABEL: define {{.*}}@_Z10test_basicv + // LLVM: alloca i8 + // LLVM: store i8 5 + + // OGCG-LABEL: define {{.*}}@_Z10test_basicv + // OGCG: alloca i8 + // OGCG: store i8 +} + +// CIR-LABEL: cir.func {{.*}}@_Z14test_subscriptv +void test_subscript() { + bool4 v = {true, false, true, false}; + // CIR: %[[LOAD:.*]] = cir.load{{.*}}!u8i + // CIR: %[[IDX:.*]] = cir.const #cir.int<2> + // CIR: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD]]{{.*}}, %[[IDX]] + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + // CIR: cir.cmp(ne,{{.*}}){{.*}}!cir.bool + bool b = v[2]; + + // LLVM-LABEL: define {{.*}}@_Z14test_subscriptv + // LLVM: lshr i8 + // LLVM: and i8 + // LLVM: icmp ne i8 + + // OGCG-LABEL: define {{.*}}@_Z14test_subscriptv + // OGCG: extractelement + // OGCG: zext i1 +} + +// CIR-LABEL: cir.func {{.*}}@_Z8test_ops +void test_ops(bool4 a, bool4 b) { + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + bool4 c = a & b; + + // LLVM-LABEL: define {{.*}}@_Z8test_opsDv4_bS_ + // LLVM: and i8 + + // OGCG-LABEL: define {{.*}}@_Z8test_opsDv4_bS_ + // OGCG: shufflevector + // OGCG: and <4 x i1> +} + +// Test bitwise operations +// CIR-LABEL: cir.func {{.*}}@_Z16test_bitwise_opsv +void test_bitwise_ops() { + bool4 a = {true, false, true, false}; + bool4 b = {false, true, true, false}; + + // Bitwise AND + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + bool4 c = a & b; + + // Bitwise OR + // CIR: cir.binop(or,{{.*}}){{.*}}!u8i + bool4 d = a | b; + + // Bitwise XOR + // CIR: cir.binop(xor,{{.*}}){{.*}}!u8i + bool4 e = a ^ b; + + // LLVM-LABEL: define {{.*}}@_Z16test_bitwise_opsv + // LLVM: and i8 + // LLVM: or i8 + // LLVM: xor i8 + + // OGCG-LABEL: define {{.*}}@_Z16test_bitwise_opsv + // OGCG: and <4 x i1> + // OGCG: or <4 x i1> + // OGCG: xor <4 x i1> +} + +// Test different vector sizes +// CIR-LABEL: cir.func {{.*}}@_Z17test_vector_sizesv +void test_vector_sizes() { + // bool2 uses u8i (padded to 8 bits minimum) + // CIR: cir.alloca !u8i, !cir.ptr, ["v2" + bool2 v2 = {true, false}; + // CIR-DAG: cir.const #cir.int<1> : !u8i + // CIR-DAG: cir.store{{.*}}!u8i, !cir.ptr + + // bool16 uses u16i + // CIR-DAG: cir.alloca !u16i, !cir.ptr, ["v16" + bool16 v16 = {true, false, true, false, true, false, true, false, + false, true, false, true, false, true, false, true}; + // CIR-DAG: cir.const #cir.int<43605> : !u16i + // CIR-DAG: cir.store{{.*}}!u16i, !cir.ptr + + // LLVM-LABEL: define {{.*}}@_Z17test_vector_sizesv + // LLVM-DAG: alloca i8 + // LLVM-DAG: store i8 1 + // LLVM-DAG: alloca i16 + // LLVM-DAG: store i16 + + // OGCG-LABEL: define {{.*}}@_Z17test_vector_sizesv + // OGCG-DAG: alloca i8 + // OGCG-DAG: store i8{{.*}}, ptr % + // OGCG-DAG: alloca i16 + // OGCG-DAG: store i16 +} + +// Test function parameters and returns +// CIR-LABEL: cir.func {{.*}}@_Z12invert_bool4 +// CIR-SAME: %arg0: !u8i +// CIR-SAME: -> !u8i +bool4 invert_bool4(bool4 v) { + // Bitwise NOT + // CIR: %[[LOAD:.*]] = cir.load{{.*}}!u8i + // CIR: cir.unary(not, %[[LOAD]]){{.*}}!u8i + return ~v; + + // LLVM-LABEL: define {{.*}}@_Z12invert_bool4Dv4_b + // LLVM: xor i8 + + // OGCG-LABEL: define {{.*}}@_Z12invert_bool4Dv4_b + // OGCG: xor <4 x i1> +} + +// Test all bits set and all bits clear +// CIR-LABEL: cir.func {{.*}}@_Z15test_edge_casesv +void test_edge_cases() { + // All false (0) + // CIR-DAG: cir.alloca !u8i, !cir.ptr, ["all_false" + bool4 all_false = {false, false, false, false}; + // CIR-DAG: cir.const #cir.int<0> : !u8i + // CIR-DAG: cir.store{{.*}}!u8i, !cir.ptr + + // All true (15 = 0b1111 for 4 bits) + // CIR-DAG: cir.alloca !u8i, !cir.ptr, ["all_true" + bool4 all_true = {true, true, true, true}; + // CIR-DAG: cir.const #cir.int<15> : !u8i + // CIR-DAG: cir.store{{.*}}!u8i, !cir.ptr +} + +// Test subscript with variable index +// CIR-LABEL: cir.func {{.*}}@_Z23test_variable_subscript +void test_variable_subscript(int idx) { + bool4 v = {true, false, true, false}; + // CIR: cir.load{{.*}}!u8i + // CIR: cir.load{{.*}}!s32i + // CIR: cir.shift(right,{{.*}}){{.*}}!u8i + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + bool b = v[idx]; +} + +// Test initialization with all same value +// CIR-LABEL: cir.func {{.*}}@_Z18test_same_init_valv +void test_same_init_val() { + // Initialize all elements to true using splat + // CIR: cir.alloca !u8i, !cir.ptr, ["v" + bool4 v = {true, true, true, true}; + // CIR: cir.const #cir.int<15> : !u8i + // CIR: cir.store{{.*}}!u8i, !cir.ptr +} + +// Test multiple operations in sequence +// CIR-LABEL: cir.func {{.*}}@_Z17test_multiple_opsv +void test_multiple_ops() { + bool4 a = {true, false, true, false}; + bool4 b = {false, true, true, false}; + + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + bool4 c = a & b; + // CIR: cir.binop(or,{{.*}}){{.*}}!u8i + bool4 d = c | b; + // CIR: cir.unary(not,{{.*}}){{.*}}!u8i + bool4 e = ~d; +} + +// Test reading specific elements +// CIR-LABEL: cir.func {{.*}}@_Z18test_read_elementsv +void test_read_elements() { + bool4 v = {true, false, true, false}; + + // Read element 0 + // CIR: cir.load{{.*}}!u8i + // CIR: cir.const #cir.int<0> + // CIR: cir.shift(right,{{.*}}){{.*}}!u8i + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + bool e0 = v[0]; + + // Read element 3 + // CIR: cir.load{{.*}}!u8i + // CIR: cir.const #cir.int<3> + // CIR: cir.shift(right,{{.*}}){{.*}}!u8i + // CIR: cir.binop(and,{{.*}}){{.*}}!u8i + bool e3 = v[3]; +}