https://github.com/adams381 created 
https://github.com/llvm/llvm-project/pull/230280

Bool vectors are now sized one bit per element, as clang does, and are passed 
and returned the way classic CodeGen passes them, including in records.

Assisted-by: Cursor / Claude Opus 5.5


>From 9b19b9a65dee5df9719062838def2a72301b18d8 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Thu, 8 Oct 2026 13:26:22 -0700
Subject: [PATCH] [CIR] Support bool vectors in x86_64 calling-convention
 lowering

Bool vectors are now sized one bit per element, as clang does, and are
passed and returned the way classic CodeGen passes them, including in
records.

Assisted-by: Cursor / Claude Opus 5.5
---
 .../include/clang/CIR/Dialect/IR/CIRTypes.td  |  11 +
 clang/lib/CIR/CodeGen/CIRGenCall.cpp          |  15 +-
 .../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp |  24 ++
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         |  12 +
 .../Transforms/CallConvLoweringPass.cpp       |  21 +-
 .../TargetLowering/CIRABIRewriteContext.cpp   | 126 +++++----
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |   8 +-
 clang/lib/CIR/Lowering/LoweringHelpers.cpp    |   5 +-
 .../CodeGen/bool-vector-record-storage-nyi.c  |  40 +++
 ...onv-lowering-x86_64-bool-vector-return.cpp |  38 +++
 .../call-conv-lowering-x86_64-bool-vector.c   | 246 ++++++++++++++++++
 .../builtins-elementwise-bool-nyi.c           |   4 +-
 .../Transforms/abi-lowering/x86_64-vector.cir |  28 ++
 13 files changed, 504 insertions(+), 74 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/bool-vector-record-storage-nyi.c
 create mode 100644 
clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector-return.cpp
 create mode 100644 
clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector.c

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td 
b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index fd092c3ebb7056..2b76d1cb6928db 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -509,6 +509,11 @@ def CIR_VectorType : CIR_Type<"Vector", "vector", [
     !cir.vector<2 x !cir.float>
     ```
 
+    A fixed-length vector of `!cir.bool` is packed one bit per element, and its
+    size is rounded up to a power of two of at least one byte, so
+    `!cir.vector<4 x !cir.bool>` occupies one byte and
+    `!cir.vector<17 x !cir.bool>` occupies four.
+
     Scalable vectors are indicated by enclosing size in square brackets.
 
     Example:
@@ -532,6 +537,12 @@ def CIR_VectorType : CIR_Type<"Vector", "vector", [
     }]>,
   ];
 
+  let extraClassDeclaration = [{
+    /// The width of the integer a vector of `!cir.bool` is stored as: one bit
+    /// per element, at least a byte.
+    uint64_t getBoolStorageWidth() const;
+  }];
+
   let extraClassDefinition = [{
     bool $cppClass::isSized() const {
       return ::cir::isSized(getElementType());
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 4f4e10aa218be9..ea900b00413777 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -606,10 +606,19 @@ static bool determineNoUndef(QualType clangTy, 
CIRGenTypes &types,
     // bits from the perspective of LLVM IR.
     return false;
 
+  // Classic CodeGen stores a packed bool vector as an integer with one bit per
+  // element, at least a byte wide, so the check above drops noundef when that
+  // integer is not a whole number of bytes.  The CIR vector type is sized to a
+  // power of two of at least a byte, which always passes that check, so test
+  // the storage integer's width directly.
+  if (clangTy->isPackedVectorBoolType(types.getASTContext()) &&
+      mlir::cast<cir::VectorType>(ty).getBoolStorageWidth() % 8)
+    return false;
+
   assert(!cir::MissingFeatures::opCallCallConv());
-  // TODO(cir): The calling convention code needs to figure if the
-  // coerced-to-type is larger than the actual type, and remove the noundef
-  // attribute. Classic compiler did it here.
+  // The coerced type is not known until CallConvLowering, which drops noundef
+  // when it is wider than the value's memory type, the check classic
+  // DetermineNoUndef makes at this point.
   if (clangTy->isBitIntType())
     return true;
   if (clangTy->isReferenceType())
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp 
b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 4bb483fc57a962..1d324a23fcee1e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -249,9 +249,33 @@ struct CIRRecordLowering final {
       cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
                                          "getStorageType for bitfields");
     }
+    if (hasBoolVectorStorageAlignMismatch(type))
+      cirGenTypes.getCGModule().errorNYI(
+          fieldDecl->getSourceRange(),
+          "getStorageType for bool vector whose storage integer is aligned "
+          "differently from the vector");
     return type;
   }
 
+  /// Whether \p type, or the innermost element type of an array \p type, is a
+  /// bool vector whose storage integer is aligned differently from the
+  /// vector.  A bool vector is stored as an integer with one bit per element,
+  /// at least a byte wide.  The vector is aligned to its own size, so an
+  /// integer with the same alignment also has the same allocation size.
+  /// Classic CodeGen lays a mismatched member out as that integer and fills
+  /// the difference with padding bytes, which this layout does not do yet.
+  bool hasBoolVectorStorageAlignMismatch(mlir::Type type) {
+    while (auto arrTy = mlir::dyn_cast<cir::ArrayType>(type))
+      type = arrTy.getElementType();
+    auto vecTy = mlir::dyn_cast<cir::VectorType>(type);
+    if (!vecTy || !mlir::isa<cir::BoolType>(vecTy.getElementType()))
+      return false;
+    auto storageTy =
+        mlir::IntegerType::get(type.getContext(), vecTy.getBoolStorageWidth());
+    return dataLayout.layout.getTypeABIAlignment(storageTy) !=
+           dataLayout.layout.getTypeABIAlignment(vecTy);
+  }
+
   uint64_t getFieldBitOffset(const FieldDecl *fieldDecl) {
     return astRecordLayout.getFieldOffset(fieldDecl->getFieldIndex());
   }
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp 
b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index fe3f41a226e00e..75677d05796e62 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -1494,10 +1494,22 @@ BitFieldType::getABIAlignment(const mlir::DataLayout 
&dataLayout,
 llvm::TypeSize cir::VectorType::getTypeSizeInBits(
     const ::mlir::DataLayout &dataLayout,
     ::mlir::DataLayoutEntryListRef params) const {
+  // Clang packs a fixed-length bool vector one bit per element and rounds the
+  // vector's size up to a power of two of at least a byte.
+  if (mlir::isa<cir::BoolType>(getElementType()) && !getIsScalable()) {
+    assert(!cir::MissingFeatures::hlsl());
+    return llvm::TypeSize::getFixed(llvm::PowerOf2Ceil(getBoolStorageWidth()));
+  }
   return llvm::TypeSize::getFixed(
       getSize() * dataLayout.getTypeSizeInBits(getElementType()));
 }
 
+uint64_t cir::VectorType::getBoolStorageWidth() const {
+  assert(mlir::isa<cir::BoolType>(getElementType()) &&
+         "only a bool vector is stored as an integer");
+  return std::max<uint64_t>(getSize(), 8);
+}
+
 uint64_t
 cir::VectorType::getABIAlignment(const ::mlir::DataLayout &dataLayout,
                                  ::mlir::DataLayoutEntryListRef params) const {
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 402cd09afd6370..c924529945c01e 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -205,11 +205,10 @@ static bool isSupportedType(mlir::Type ty, const 
DataLayout &dl) {
     // x86_64 has no calling convention for one.
     if (vecTy.getIsScalable())
       return false;
-    // The classifier sizes a vector as element count times element width, so
-    // an element is only usable where that width is the one clang gives it.
-    // It is not for bool (a bit to clang, a byte here), for a _BitInt narrower
-    // than a byte (clang rounds to the storage container), or for x87 long
-    // double (80 bits here against clang's 128).
+    // mapCIRType maps a bool as a one-bit integer, so a bool vector is sized
+    // one bit per element, as clang packs it.  Vectors of a _BitInt whose
+    // width is not a whole number of bytes, or of x87 long double, are not
+    // handled yet.
     mlir::Type elemTy = vecTy.getElementType();
     if (auto elemInt = dyn_cast<cir::IntType>(elemTy)) {
       if (elemInt.getWidth() % 8)
@@ -217,7 +216,7 @@ static bool isSupportedType(mlir::Type ty, const DataLayout 
&dl) {
     } else if (auto elemFp = dyn_cast<cir::FPTypeInterface>(elemTy)) {
       if (&elemFp.getFloatSemantics() == &llvm::APFloat::x87DoubleExtended())
         return false;
-    } else {
+    } else if (!isa<cir::BoolType>(elemTy)) {
       return false;
     }
     // Clang also rounds the vector's own width up to a power of two, and the
@@ -308,7 +307,10 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, 
MLIRContext *ctx) {
   return llvm::TypeSwitch<const llvm::abi::Type *, mlir::Type>(ty)
       .Case(
           [&](const llvm::abi::VoidType *) { return cir::VoidType::get(ctx); })
-      .Case([&](const llvm::abi::IntegerType *intTy) {
+      .Case([&](const llvm::abi::IntegerType *intTy) -> mlir::Type {
+        // mapCIRType maps a bool as a one-bit integer.
+        if (intTy->isBool())
+          return cir::BoolType::get(ctx);
         return cir::IntType::get(ctx, intTy->getSizeInBits().getFixedValue(),
                                  intTy->isSigned(), intTy->isBitInt());
       })
@@ -373,8 +375,9 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
                                  llvm::Align(dl.getTypeABIAlignment(type)));
       })
       .Case([&](cir::BoolType) {
-        return tb.getIntegerType(dl.getTypeSizeInBits(type),
-                                 llvm::Align(dl.getTypeABIAlignment(type)),
+        // A bool is a one-bit integer to the classifier, as QualTypeMapper
+        // maps it, so a bool vector is sized one bit per element.
+        return tb.getIntegerType(1, llvm::Align(dl.getTypeABIAlignment(type)),
                                  /*Signed=*/false);
       })
       .Case([&](cir::VoidType) { return tb.getVoidType(); })
diff --git 
a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp 
b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
index f79a8535355a85..75eaec4d169866 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
@@ -185,9 +185,36 @@ mlir::Value createIgnoredValue(mlir::OpBuilder &builder, 
mlir::Location loc,
   return cir::ConstantOp::create(builder, loc, ty, cir::PoisonAttr::get(ty));
 }
 
+/// Whether \p coercedTy is wider than the type a value of type \p origTy has
+/// in memory, so the coerced value passes bits the value does not define and
+/// cannot be noundef.  This is the comparison classic CodeGen makes.  A bool
+/// vector's memory type is its storage integer, one bit per element and at
+/// least a byte wide, not its power-of-two size.
+static bool isCoercionWiderThanMemory(mlir::Type origTy, mlir::Type coercedTy,
+                                      const mlir::DataLayout &dl) {
+  uint64_t memoryBits = dl.getTypeSizeInBits(origTy).getFixedValue();
+  if (auto vecTy = mlir::dyn_cast<cir::VectorType>(origTy);
+      vecTy && mlir::isa<cir::BoolType>(vecTy.getElementType()))
+    memoryBits = vecTy.getBoolStorageWidth();
+  return dl.getTypeSizeInBits(coercedTy).getFixedValue() > memoryBits;
+}
+
+/// Whether updateArgAttrs has anything to change: an arg that is Ignore (its
+/// slot is dropped), Extend (needs llvm.signext / llvm.zeroext), Indirect
+/// (needs the pointer attributes), Expand (changes the argument count), or a
+/// coerced Direct, which may be flattened or may lose llvm.noundef.
+static bool needsArgAttrUpdate(const FunctionClassification &fc) {
+  return llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
+    return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
+           ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
+           (ac.kind == ArgKind::Direct && ac.coercedType);
+  });
+}
+
 /// Build an updated arg_attrs ArrayAttr that drops Ignore'd args, adds
-/// llvm.signext / llvm.zeroext on Extend args, and adds the pointer
-/// attributes for Indirect args.  Preserves any existing arg attributes on
+/// llvm.signext / llvm.zeroext on Extend args, adds the pointer attributes
+/// for Indirect args, and drops llvm.noundef from a coerced Direct arg that
+/// passes undefined bits.  Preserves any other existing arg attributes on
 /// retained arg slots.  \p origArgTypes provides the pre-rewrite type for
 /// each arg slot.
 mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
@@ -255,6 +282,12 @@ mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
                       dl.getTypeSize(pointeeTy).getFixedValue()));
       }
       newArgAttrs.push_back(attrs.getDictionary(ctx));
+    } else if (ac.kind == ArgKind::Direct && ac.coercedType &&
+               isCoercionWiderThanMemory(origArgTypes[oldIdx], ac.coercedType,
+                                         dl)) {
+      mlir::NamedAttrList attrs(existing);
+      attrs.erase(mlir::LLVM::LLVMDialect::getNoUndefAttrName());
+      newArgAttrs.push_back(attrs.getDictionary(ctx));
     } else {
       newArgAttrs.push_back(existing);
     }
@@ -264,22 +297,31 @@ mlir::ArrayAttr updateArgAttrs(mlir::MLIRContext *ctx,
 
 /// Build an updated res_attrs ArrayAttr (single entry, since CIR funcs have
 /// at most one result) that adds llvm.signext / llvm.zeroext on an Extend
-/// return.  Preserves any existing res attributes.
+/// return and drops llvm.noundef from a coerced return that passes undefined
+/// bits.  Preserves any other existing res attributes.  Returns
+/// \p existingResAttrs unchanged, which may be null, when there is nothing to
+/// add or drop.
 mlir::ArrayAttr updateResAttrs(mlir::MLIRContext *ctx,
                                mlir::ArrayAttr existingResAttrs,
-                               const ArgClassification &retInfo) {
-  if (retInfo.kind != ArgKind::Extend)
+                               const ArgClassification &retInfo,
+                               mlir::Type origRetTy,
+                               const mlir::DataLayout &dl) {
+  bool needsNoUndefDrop =
+      retInfo.kind == ArgKind::Direct && retInfo.coercedType &&
+      isCoercionWiderThanMemory(origRetTy, retInfo.coercedType, dl);
+  if (retInfo.kind != ArgKind::Extend && !needsNoUndefDrop)
     return existingResAttrs;
 
-  SmallVector<mlir::NamedAttribute> attrs;
+  mlir::NamedAttrList attrs;
   if (existingResAttrs && !existingResAttrs.empty())
-    for (mlir::NamedAttribute na :
-         mlir::cast<mlir::DictionaryAttr>(existingResAttrs[0]))
-      attrs.push_back(na);
-  StringRef attrName = retInfo.signExtend ? "llvm.signext" : "llvm.zeroext";
-  attrs.push_back(mlir::NamedAttribute(mlir::StringAttr::get(ctx, attrName),
-                                       mlir::UnitAttr::get(ctx)));
-  return mlir::ArrayAttr::get(ctx, {mlir::DictionaryAttr::get(ctx, attrs)});
+    attrs.append(
+        mlir::cast<mlir::DictionaryAttr>(existingResAttrs[0]).getValue());
+  if (retInfo.kind == ArgKind::Extend)
+    attrs.set(retInfo.signExtend ? "llvm.signext" : "llvm.zeroext",
+              mlir::UnitAttr::get(ctx));
+  if (needsNoUndefDrop)
+    attrs.erase(mlir::LLVM::LLVMDialect::getNoUndefAttrName());
+  return mlir::ArrayAttr::get(ctx, {attrs.getDictionary(ctx)});
 }
 
 /// The number of bytes a coercion memory slot needs to hold a value of type
@@ -1259,19 +1301,11 @@ void rewriteIndirectReturnCall(cir::CallOp call,
   copyCallAttributes(call, newCall);
   newCall->removeAttr("res_attrs");
 
-  // Shape the per-argument attrs exactly as the non-sret path does
-  // (signext / zeroext for Extend, drop Ignore slots, byval / align for
-  // Indirect, flatten for Expand and Direct+canFlatten) before prepending the
-  // sret slot, so sret composes correctly with Extend / Ignore / Indirect /
-  // Expand / Direct+canFlatten args.
+  // Shape the per-argument attrs exactly as the non-sret path does, through
+  // updateArgAttrs, before prepending the sret slot, so sret composes with
+  // every argument classification.
   mlir::ArrayAttr argAttrs = call->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
-  bool needsArgAttrUpdate =
-      llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
-        return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
-               ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
-               getFlattenedCoercedType(ac);
-      });
-  if (needsArgAttrUpdate)
+  if (needsArgAttrUpdate(fc))
     argAttrs = updateArgAttrs(ctx, origCallArgTypes, argAttrs, fc, dl);
   applySretSlotAttrs(newCall, argAttrs, origRetTy, sretAlign, builder);
 
@@ -1609,17 +1643,8 @@ mlir::LogicalResult 
CIRABIRewriteContext::rewriteFunctionDefinition(
   funcOp.setFunctionTypeAttr(mlir::TypeAttr::get(newFnTy));
 
   // Rebuild arg_attrs when the function has an sret slot (slot 0 needs the
-  // sret attribute set) or any arg is Ignore (dropped from the output array),
-  // Extend (needs llvm.signext / llvm.zeroext), Indirect (gains the pointer
-  // attributes updateArgAttrs applies), Expand or Direct+canFlatten (both
-  // change the argument count).
-  bool needsArgAttrUpdate =
-      hasSRet || llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
-        return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
-               ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
-               getFlattenedCoercedType(ac);
-      });
-  if (needsArgAttrUpdate) {
+  // sret attribute set) or any arg needs updateArgAttrs.
+  if (hasSRet || needsArgAttrUpdate(fc)) {
     auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
     mlir::ArrayAttr updated =
         updateArgAttrs(ctx, oldArgTypes, existing, fc, dl);
@@ -1641,10 +1666,12 @@ mlir::LogicalResult 
CIRABIRewriteContext::rewriteFunctionDefinition(
 
   if (mlir::isa<cir::VoidType>(newRetTy)) {
     funcOp->removeAttr("res_attrs");
-  } else if (fc.returnInfo.kind == ArgKind::Extend) {
-    // Layer llvm.signext / llvm.zeroext onto an Extend return.
+  } else if (fc.returnInfo.kind == ArgKind::Extend ||
+             fc.returnInfo.kind == ArgKind::Direct) {
     auto existing = funcOp->getAttrOfType<mlir::ArrayAttr>("res_attrs");
-    funcOp->setAttr("res_attrs", updateResAttrs(ctx, existing, fc.returnInfo));
+    if (mlir::ArrayAttr updated =
+            updateResAttrs(ctx, existing, fc.returnInfo, origRetTy, dl))
+      funcOp->setAttr("res_attrs", updated);
   }
 
   return mlir::success();
@@ -1872,24 +1899,19 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation 
*callOp,
     call.getResult().replaceAllUsesWith(coercedBack);
   }
 
-  // Layer llvm.signext / llvm.zeroext onto the new call's arg_attrs and
-  // res_attrs for Extend args/return.  Ignore args require a rebuild because
-  // their slots are dropped; Indirect args need llvm.byval / llvm.align;
-  // Expand and Direct+canFlatten args change the argument count.
-  bool needsArgAttrUpdate =
-      llvm::any_of(fc.argInfos, [](const ArgClassification &ac) {
-        return ac.kind == ArgKind::Ignore || ac.kind == ArgKind::Extend ||
-               ac.kind == ArgKind::Indirect || ac.kind == ArgKind::Expand ||
-               getFlattenedCoercedType(ac);
-      });
-  if (needsArgAttrUpdate) {
+  if (needsArgAttrUpdate(fc)) {
     auto existing = call->getAttrOfType<mlir::ArrayAttr>("arg_attrs");
     newCall->setAttr("arg_attrs",
                      updateArgAttrs(ctx, origCallArgTypes, existing, fc, dl));
   }
-  if (fc.returnInfo.kind == ArgKind::Extend) {
+  // Add llvm.signext / llvm.zeroext to an Extend return, and drop llvm.noundef
+  // from a coerced Direct return that passes undefined bits.
+  if (fc.returnInfo.kind == ArgKind::Extend ||
+      (hasResult && fc.returnInfo.kind == ArgKind::Direct)) {
     auto existing = call->getAttrOfType<mlir::ArrayAttr>("res_attrs");
-    newCall->setAttr("res_attrs", updateResAttrs(ctx, existing, 
fc.returnInfo));
+    if (mlir::ArrayAttr updated =
+            updateResAttrs(ctx, existing, fc.returnInfo, origRetTy, dl))
+      newCall->setAttr("res_attrs", updated);
   } else if (hasResult && mlir::isa<cir::VoidType>(callRetTy)) {
     newCall->removeAttr("res_attrs");
   }
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index a254d798362290..4a49b252515496 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -220,10 +220,10 @@ static mlir::Value 
emitToMemory(mlir::ConversionPatternRewriter &rewriter,
         resultTy = mlir::VectorType::get(
             vecTy.getSize(), vecTy.getElementType(), vecTy.getIsScalable());
       else {
-        uint64_t bytePadded = std::max<uint64_t>(vecTy.getSize(), 8);
-        resultTy = mlir::IntegerType::get(origType.getContext(), bytePadded);
-        value = emitBoolVecConversion(
-            rewriter, value, dyn_cast<mlir::IntegerType>(resultTy).getWidth());
+        resultTy = mlir::IntegerType::get(origType.getContext(),
+                                          vecTy.getBoolStorageWidth());
+        value =
+            emitBoolVecConversion(rewriter, value, 
vecTy.getBoolStorageWidth());
       }
       return mlir::LLVM::BitcastOp::create(rewriter, value.getLoc(), resultTy,
                                            value);
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp 
b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index e3295c21b6a711..c6baa2fd63e3e9 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -96,9 +96,8 @@ mlir::Type convertTypeForMemory(const mlir::TypeConverter 
&converter,
   if (auto vecTy = mlir::dyn_cast<cir::VectorType>(type)) {
     if (mlir::isa<cir::BoolType>(vecTy.getElementType())) {
       assert(!cir::MissingFeatures::hlsl());
-      // Pad to at least one byte.
-      uint64_t bytePadded = std::max<uint64_t>(vecTy.getSize(), 8);
-      return mlir::IntegerType::get(type.getContext(), bytePadded);
+      return mlir::IntegerType::get(type.getContext(),
+                                    vecTy.getBoolStorageWidth());
     }
   }
 
diff --git a/clang/test/CIR/CodeGen/bool-vector-record-storage-nyi.c 
b/clang/test/CIR/CodeGen/bool-vector-record-storage-nyi.c
new file mode 100644
index 00000000000000..e14d0ac2e6b540
--- /dev/null
+++ b/clang/test/CIR/CodeGen/bool-vector-record-storage-nyi.c
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
/dev/null -verify=b256 -DB256
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
/dev/null -verify=b300 -DB300
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
/dev/null -verify=array -DARRAY
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
/dev/null -verify=union -DUNION
+
+typedef _Bool b256 __attribute__((ext_vector_type(256)));
+typedef _Bool b300 __attribute__((ext_vector_type(300)));
+
+#ifdef B256
+// An i256 is aligned to 16 bytes where the vector is aligned to 32.
+struct S256 {
+  b256 v; // b256-error {{ClangIR code gen Not Yet Implemented: getStorageType 
for bool vector whose storage integer is aligned differently from the vector}}
+  int x;
+};
+void f256(struct S256 *s) { s->x = 1; }
+#endif
+
+#ifdef B300
+// An i300 is aligned to 16 bytes where the vector is aligned to 64.
+struct S300 {
+  b300 v; // b300-error {{ClangIR code gen Not Yet Implemented: getStorageType 
for bool vector whose storage integer is aligned differently from the vector}}
+  int x;
+};
+void f300(struct S300 *s) { s->x = 1; }
+#endif
+
+#ifdef ARRAY
+struct T {
+  b256 a[2]; // array-error {{ClangIR code gen Not Yet Implemented: 
getStorageType for bool vector whose storage integer is aligned differently 
from the vector}}
+};
+void g(struct T *t) { (void)t->a[0]; }
+#endif
+
+#ifdef UNION
+union U {
+  b256 v; // union-error {{ClangIR code gen Not Yet Implemented: 
getStorageType for bool vector whose storage integer is aligned differently 
from the vector}}
+  int x;
+};
+void h(union U *u) { u->x = 1; }
+#endif
diff --git 
a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector-return.cpp 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector-return.cpp
new file mode 100644
index 00000000000000..cd38d1a03d9b81
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector-return.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o 
%t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+typedef bool b4 __attribute__((ext_vector_type(4)));
+typedef bool b17 __attribute__((ext_vector_type(17)));
+typedef bool b24 __attribute__((ext_vector_type(24)));
+
+b4 rb4(b4 v) { return v; }
+
+// CIR: cir.func {{.*}} @_Z3rb4Dv4_b(%arg0: !u8i {llvm.noundef} 
loc({{[^)]+}})) -> (!u8i {llvm.noundef})
+// LLVM: define dso_local noundef i8 @_Z3rb4Dv4_b(i8 noundef %{{[^,)]+}})
+
+// The 17-bit storage integer is not a whole number of bytes, so the return is
+// not noundef.
+b17 rb17(b17 v) { return v; }
+
+// CIR: cir.func {{.*}} @_Z4rb17Dv17_b(%arg0: !u32i loc({{[^)]+}})) -> !u32i
+// LLVM: define dso_local i32 @_Z4rb17Dv17_b(i32 %{{[^,)]+}})
+
+// The i32 is wider than the 24 bits of storage, so the return is not noundef.
+b24 rb24(b24 v) { return v; }
+
+// CIR: cir.func {{.*}} @_Z4rb24Dv24_b(%arg0: !u32i loc({{[^)]+}})) -> !u32i
+// LLVM: define dso_local i32 @_Z4rb24Dv24_b(i32 %{{[^,)]+}})
+
+b4 call_rb4(b4 v) { return rb4(v); }
+
+// CIR: cir.call @_Z3rb4Dv4_b(%{{[^)]+}}) : (!u8i {llvm.noundef}) -> (!u8i 
{llvm.noundef})
+// LLVM: call noundef i8 @_Z3rb4Dv4_b(i8 noundef %{{[^,)]+}})
+
+b24 call_rb24(b24 v) { return rb24(v); }
+
+// CIR: cir.call @_Z4rb24Dv24_b(%{{[^)]+}}) : (!u32i) -> !u32i
+// LLVM: call i32 @_Z4rb24Dv24_b(i32 %{{[^,)]+}})
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector.c 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector.c
new file mode 100644
index 00000000000000..78f5085c3b4f10
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-bool-vector.c
@@ -0,0 +1,246 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o 
%t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-feature +avx 
-fclangir -emit-llvm %s -o %t-cir-avx.ll
+// RUN: FileCheck --check-prefix=AVX --input-file=%t-cir-avx.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-feature +avx 
-emit-llvm %s -o %t-avx.ll
+// RUN: FileCheck --check-prefix=AVX --input-file=%t-avx.ll %s
+
+typedef _Bool b4 __attribute__((ext_vector_type(4)));
+typedef _Bool b8 __attribute__((ext_vector_type(8)));
+typedef _Bool b16 __attribute__((ext_vector_type(16)));
+typedef _Bool b17 __attribute__((ext_vector_type(17)));
+typedef _Bool b24 __attribute__((ext_vector_type(24)));
+typedef _Bool b40 __attribute__((ext_vector_type(40)));
+typedef _Bool b64 __attribute__((ext_vector_type(64)));
+typedef _Bool b100 __attribute__((ext_vector_type(100)));
+typedef _Bool b128 __attribute__((ext_vector_type(128)));
+typedef _Bool b130 __attribute__((ext_vector_type(130)));
+typedef _Bool b256 __attribute__((ext_vector_type(256)));
+
+// A b4 member takes one byte.
+struct SB4 { b4 v; };
+struct SB4x2 { b4 a; b4 b; int i; };
+struct SB4Arr { b4 a[3]; };
+struct SB4F { b4 v; float f; };
+struct SB64x2 { b64 a; b64 b; };
+struct SB64x3 { b64 a, b, c; };
+struct SB128 { b128 v; };
+union UB4 { b4 v; char c; };
+
+// CIR-DAG: !rec_SB4 = !cir.struct<"SB4" {data !cir.vector<4 x !cir.bool>}>
+// CIR-DAG: !rec_anon_struct = !cir.struct<{data !cir.double, data 
!cir.double}>
+// CIR-DAG: !rec_SB4x2 = !cir.struct<"SB4x2" {data !cir.vector<4 x !cir.bool>, 
data !cir.vector<4 x !cir.bool>, data !s32i}>
+// CIR-DAG: !rec_SB4F = !cir.struct<"SB4F" {data !cir.vector<4 x !cir.bool>, 
data !cir.float}>
+// CIR-DAG: !rec_SB64x3 = !cir.struct<"SB64x3" {data !cir.vector<64 x 
!cir.bool>, data !cir.vector<64 x !cir.bool>, data !cir.vector<64 x !cir.bool>}>
+// CIR-DAG: !rec_SB128 = !cir.struct<"SB128" {data !cir.vector<128 x 
!cir.bool>}>
+// LLVM-DAG: %struct.SB4 = type { i8 }
+// LLVM-DAG: %struct.SB4x2 = type { i8, i8, i32 }
+// LLVM-DAG: %struct.SB4Arr = type { [3 x i8] }
+// LLVM-DAG: %struct.SB4F = type { i8, float }
+// LLVM-DAG: %struct.SB64x2 = type { i64, i64 }
+// LLVM-DAG: %struct.SB64x3 = type { i64, i64, i64 }
+// LLVM-DAG: %struct.SB128 = type { i128 }
+// LLVM-DAG: %union.UB4 = type { i8 }
+
+b4 vb4(b4 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb4(%arg0: !u8i {llvm.noundef} loc({{[^)]+}})) -> !u8i
+// CIR: %[[SLOT:[^ ]+]] = cir.alloca "coerce" align(1) : !cir.ptr<!u8i>
+// CIR: cir.store %arg0, %[[SLOT]] : !u8i, !cir.ptr<!u8i>
+// CIR: %[[VIEW:[^ ]+]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!u8i> -> 
!cir.ptr<!cir.vector<4 x !cir.bool>>
+// CIR: cir.load %[[VIEW]] : !cir.ptr<!cir.vector<4 x !cir.bool>>, 
!cir.vector<4 x !cir.bool>
+
+// LLVM: define dso_local i8 @vb4(i8 noundef %[[ARG:[^,)]+]])
+// LLVM: store i8 %[[ARG]], ptr %[[SLOT:[^,]+]], align 1
+// LLVM: %[[BITS:[^ ]+]] = load i8, ptr %[[SLOT]], align 1
+// LLVM: bitcast i8 %[[BITS]] to <8 x i1>
+// LLVM: ret i8
+
+b8 vb8(b8 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb8(%arg0: !u8i {llvm.noundef} loc({{[^)]+}})) -> !u8i
+// LLVM: define dso_local i8 @vb8(i8 noundef %{{[^,)]+}})
+
+b16 vb16(b16 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb16(%arg0: !u16i {llvm.noundef} loc({{[^)]+}})) -> 
!u16i
+// LLVM: define dso_local i16 @vb16(i16 noundef %{{[^,)]+}})
+
+// The 17-bit storage integer is not a whole number of bytes, so the argument
+// is not noundef.
+b17 vb17(b17 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb17(%arg0: !u32i loc({{[^)]+}})) -> !u32i
+// CIR: %[[SLOT17:[^ ]+]] = cir.alloca "coerce" align(4) : !cir.ptr<!u32i>
+// CIR: cir.store %arg0, %[[SLOT17]] : !u32i, !cir.ptr<!u32i>
+// CIR: cir.cast bitcast %[[SLOT17]] : !cir.ptr<!u32i> -> 
!cir.ptr<!cir.vector<17 x !cir.bool>>
+
+// LLVM: define dso_local i32 @vb17(i32 %[[ARG17:[^,)]+]])
+// LLVM: store i32 %[[ARG17]], ptr %[[SLOT17:[^,]+]], align 4
+// LLVM: load i17, ptr %[[SLOT17]], align 4
+// LLVM: ret i32
+
+// The coerced types are wider than the 24 and 40 bits of storage, so neither
+// is noundef.
+b24 vb24(b24 v) { return v; }
+b40 vb40(b40 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb24(%arg0: !u32i loc({{[^)]+}})) -> !u32i
+// CIR: cir.func {{.*}} @vb40(%arg0: !cir.double loc({{[^)]+}})) -> !cir.double
+// LLVM: define dso_local i32 @vb24(i32 %{{[^,)]+}})
+// LLVM: define dso_local double @vb40(double %{{[^,)]+}})
+
+b24 call_vb24(b24 v) { return vb24(v); }
+
+// CIR: cir.func {{.*}} @call_vb24(%arg0: !u32i loc({{[^)]+}})) -> !u32i
+// CIR: cir.call @vb24(%{{[^)]+}}) : (!u32i) -> !u32i
+// LLVM: define dso_local i32 @call_vb24(i32 %{{[^,)]+}})
+// LLVM: call i32 @vb24(i32 %{{[^,)]+}})
+
+b64 vb64(b64 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb64(%arg0: !cir.double {llvm.noundef} 
loc({{[^)]+}})) -> !cir.double
+// LLVM: define dso_local double @vb64(double noundef %{{[^,)]+}})
+
+// A 100-bit vector is passed as itself, but its storage is not a whole number
+// of bytes, so it is not noundef.
+b100 vb100(b100 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb100(%arg0: !cir.vector<100 x !cir.bool> 
loc({{[^)]+}})) -> !cir.vector<100 x !cir.bool>
+// LLVM: define dso_local <100 x i1> @vb100(<100 x i1> %{{[^,)]+}})
+
+b128 vb128(b128 v) { return v; }
+
+// CIR: cir.func {{.*}} @vb128(%arg0: !cir.vector<128 x !cir.bool> 
{llvm.noundef} loc({{[^)]+}})) -> !cir.vector<128 x !cir.bool>
+// CIR-NOT: cir.alloca "coerce"
+// CIR: cir.return %{{.+}} : !cir.vector<128 x !cir.bool>
+
+// LLVM: define dso_local <128 x i1> @vb128(<128 x i1> noundef 
%[[ARG128:[^,)]+]])
+// LLVM: bitcast <128 x i1> %[[ARG128]] to i128
+// LLVM: ret <128 x i1>
+
+// Without AVX a 256-bit vector is passed in memory.
+void vb256(b256 v) {}
+
+// CIR: cir.func {{.*}} @vb256(%arg0: !cir.ptr<!cir.vector<256 x !cir.bool>> 
{llvm.align = 32 : i64, llvm.byval = !cir.vector<256 x !cir.bool>, 
llvm.noundef} loc({{[^)]+}}))
+// LLVM: define dso_local void @vb256(ptr noundef byval(i256) align 32 
%{{[^,)]+}})
+// AVX: define dso_local void @vb256(<256 x i1> noundef %{{[^,)]+}})
+
+struct SB4 sb4(struct SB4 s) { return s; }
+
+// CIR: cir.func {{.*}} @sb4(%arg0: !u8i loc({{[^)]+}})) -> !u8i
+// LLVM: define dso_local i8 @sb4(i8 %{{[^,)]+}})
+
+struct SB4x2 sb4x2(struct SB4x2 s) { return s; }
+
+// CIR: cir.func {{.*}} @sb4x2(%arg0: !u64i loc({{[^)]+}})) -> !u64i
+// LLVM: define dso_local i64 @sb4x2(i64 %{{[^,)]+}})
+
+struct SB4Arr sb4arr(struct SB4Arr s) { return s; }
+
+// CIR: cir.func {{.*}} @sb4arr(%arg0: !cir.int<u, 24> loc({{[^)]+}})) -> 
!cir.int<u, 24>
+// LLVM: define dso_local i24 @sb4arr(i24 %{{[^,)]+}})
+
+struct SB64x2 sb64x2(struct SB64x2 s) { return s; }
+
+// CIR: cir.func {{.*}} @sb64x2(%arg0: !cir.double loc({{[^)]+}}), %arg1: 
!cir.double loc({{[^)]+}})) -> !rec_anon_struct
+// LLVM: define dso_local { double, double } @sb64x2(double %{{[^,)]+}}, 
double %{{[^,)]+}})
+
+union UB4 ub4(union UB4 u) { return u; }
+
+// CIR: cir.func {{.*}} @ub4(%arg0: !s8i loc({{[^)]+}})) -> !s8i
+// LLVM: define dso_local i8 @ub4(i8 %{{[^,)]+}})
+
+// Past the integer registers a bool vector still coerces to an integer.
+void stack_vb4(long a, long b, long c, long d, long e, long f, b4 v) {}
+
+// CIR: cir.func {{.*}} @stack_vb4(%arg0: !s64i {llvm.noundef} loc({{[^)]+}}), 
%arg1: !s64i {llvm.noundef} loc({{[^)]+}}), %arg2: !s64i {llvm.noundef} 
loc({{[^)]+}}), %arg3: !s64i {llvm.noundef} loc({{[^)]+}}), %arg4: !s64i 
{llvm.noundef} loc({{[^)]+}}), %arg5: !s64i {llvm.noundef} loc({{[^)]+}}), 
%arg6: !u8i {llvm.noundef} loc({{[^)]+}}))
+// LLVM: define dso_local void @stack_vb4(i64 noundef %{{[^,)]+}}, i64 noundef 
%{{[^,)]+}}, i64 noundef %{{[^,)]+}}, i64 noundef %{{[^,)]+}}, i64 noundef 
%{{[^,)]+}}, i64 noundef %{{[^,)]+}}, i8 noundef %{{[^,)]+}})
+
+b4 call_vb4(b4 v) { return vb4(v); }
+
+// CIR: cir.func {{.*}} @call_vb4(%arg0: !u8i {llvm.noundef} loc({{[^)]+}})) 
-> !u8i
+// CIR: cir.call @vb4(%{{[^)]+}}) : (!u8i {llvm.noundef}) -> !u8i
+
+// LLVM: define dso_local i8 @call_vb4(i8 noundef %{{[^,)]+}})
+// LLVM: call i8 @vb4(i8 noundef %{{[^,)]+}})
+
+void var(int n, ...);
+void call_var(b4 v) { var(1, v); }
+
+// CIR: cir.func {{.*}} @call_var(%arg0: !u8i {llvm.noundef} loc({{[^)]+}}))
+// CIR: cir.call @var(%{{[^,]+}}, %{{[^)]+}}) : (!s32i {llvm.noundef}, !u8i 
{llvm.noundef}) -> ()
+// LLVM: call void (i32, ...) @var(i32 noundef 1, i8 noundef %{{[^,)]+}})
+
+void call_var24(b24 v) { var(1, v); }
+
+// CIR: cir.call @var(%{{[^,]+}}, %{{[^)]+}}) : (!s32i {llvm.noundef}, !u32i) 
-> ()
+// LLVM: call void (i32, ...) @var(i32 noundef 1, i32 %{{[^,)]+}})
+
+b128 call_vb128(b128 v) { return vb128(v); }
+
+// CIR: cir.call @vb128(%{{[^)]+}}) : (!cir.vector<128 x !cir.bool> 
{llvm.noundef}) -> !cir.vector<128 x !cir.bool>
+// LLVM: call <128 x i1> @vb128(<128 x i1> noundef %{{[^,)]+}})
+
+b40 call_vb40(b40 v) { return vb40(v); }
+
+// CIR: cir.call @vb40(%{{[^)]+}}) : (!cir.double) -> !cir.double
+// LLVM: call double @vb40(double %{{[^,)]+}})
+
+void call_vb256(b256 v) { vb256(v); }
+
+// CIR: cir.call @vb256(%{{[^)]+}}) : (!cir.ptr<!cir.vector<256 x !cir.bool>> 
{llvm.align = 32 : i64, llvm.byval = !cir.vector<256 x !cir.bool>, 
llvm.noundef}) -> ()
+// LLVM: call void @vb256(ptr noundef byval(i256) align 32 %{{[^,)]+}})
+// AVX: call void @vb256(<256 x i1> noundef %{{[^,)]+}})
+
+// The byval pointer is noundef even though the 130-bit storage is not a whole
+// number of bytes.
+void vb130(b130 v) {}
+
+// CIR: cir.func {{.*}} @vb130(%arg0: !cir.ptr<!cir.vector<130 x !cir.bool>> 
{llvm.align = 32 : i64, llvm.byval = !cir.vector<130 x !cir.bool>, 
llvm.noundef} loc({{[^)]+}}))
+// LLVM: define dso_local void @vb130(ptr noundef byval(i130) align 32 
%{{[^,)]+}})
+
+// A record of one 128-bit bool vector is coerced to the vector.
+struct SB128 sb128(struct SB128 s) { return s; }
+
+// CIR: cir.func {{.*}} @sb128(%arg0: !cir.vector<128 x !cir.bool> 
loc({{[^)]+}})) -> !cir.vector<128 x !cir.bool>
+// LLVM: define dso_local <128 x i1> @sb128(<128 x i1> %{{[^,)]+}})
+
+struct SB4F sb4f(struct SB4F s) { return s; }
+
+// CIR: cir.func {{.*}} @sb4f(%arg0: !u64i loc({{[^)]+}})) -> !u64i
+// LLVM: define dso_local i64 @sb4f(i64 %{{[^,)]+}})
+
+struct SB64x3 sb64x3(struct SB64x3 s) { return s; }
+
+// CIR: cir.func {{.*}} @sb64x3(%arg0: !cir.ptr<!rec_SB64x3> {llvm.align = 8 : 
i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_SB64x3, llvm.writable} 
loc({{[^)]+}}), %arg1: !cir.ptr<!rec_SB64x3> {llvm.align = 8 : i64, llvm.byval 
= !rec_SB64x3, llvm.noundef} loc({{[^)]+}}))
+// LLVM: define dso_local void @sb64x3(ptr dead_on_unwind noalias writable 
sret(%struct.SB64x3) align 8 %{{[^,)]+}}, ptr noundef byval(%struct.SB64x3) 
align 8 %{{[^,)]+}})
+
+// A call returning through an sret slot drops noundef from a coerced
+// argument too.
+struct Big { long a, b, c; };
+struct Big mk24(b24 v);
+struct Big sret_vb24(b24 v) { return mk24(v); }
+
+// CIR: cir.func {{.*}} @sret_vb24(%arg0: !cir.ptr<!rec_Big> {llvm.align = 8 : 
i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Big, llvm.writable} 
loc({{[^)]+}}), %arg1: !u32i loc({{[^)]+}}))
+// CIR: cir.call @mk24(%arg0, %{{[^)]+}}) : (!cir.ptr<!rec_Big> {llvm.align = 
8 : i64, llvm.dead_on_unwind, llvm.sret = !rec_Big, llvm.writable}, !u32i) -> ()
+// LLVM: define dso_local void @sret_vb24(ptr dead_on_unwind noalias writable 
sret(%struct.Big) align 8 %{{[^,)]+}}, i32 %{{[^,)]+}})
+// LLVM: call void @mk24(ptr dead_on_unwind writable sret(%struct.Big) align 8 
%{{[^,)]+}}, i32 %{{[^,)]+}})
+
+b4 get_va4(int n, ...) {
+  __builtin_va_list ap;
+  __builtin_va_start(ap, n);
+  b4 v = __builtin_va_arg(ap, b4);
+  __builtin_va_end(ap);
+  return v;
+}
+
+// CIR: cir.func {{.*}} @get_va4(%arg0: !s32i {llvm.noundef} loc({{[^)]+}}), 
...) -> !u8i
+// LLVM: define dso_local i8 @get_va4(i32 noundef %{{[^,)]+}}, ...)
+// LLVM: icmp ule i32 %{{[^,]+}}, 40
+// LLVM: add i32 %{{[^,]+}}, 8
+// LLVM: load i8, ptr %{{[^,]+}}, align 1
diff --git a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c 
b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c
index 73b31e8f5b5691..933a1298ae5b9b 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c
+++ b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c
@@ -1,6 +1,4 @@
-// TODO(cir): drop -fno-clangir-call-conv-lowering once CallConvLowering
-// supports vector types.
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir 
-fno-clangir-call-conv-lowering %s -verify -emit-cir -o -
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir %s -verify 
-emit-cir -o -
 
 typedef _Bool vbool4 __attribute__((ext_vector_type(4)));
 
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir
index d5d47a8491b44d..56f472e47f83e1 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-vector.cir
@@ -105,6 +105,31 @@ module attributes {
 
   // CHECK: cir.func{{.*}} @take_union_floats(%arg0: !cir.vector<2 x 
!cir.float>)
 
+  // A bool vector is packed one bit per element, so four bools occupy one byte
+  // and are passed as an i8, which keeps noundef.
+  cir.func @vbool4(%arg0: !cir.vector<4 x !cir.bool> {llvm.noundef}) -> 
(!cir.vector<4 x !cir.bool> {llvm.noundef}) {
+    cir.return %arg0 : !cir.vector<4 x !cir.bool>
+  }
+
+  // CHECK: cir.func{{.*}} @vbool4(%arg0: !u8i {llvm.noundef}) -> (!u8i 
{llvm.noundef})
+
+  // Twenty-four bools are passed as an i32, whose top byte is undefined, so
+  // noundef is dropped from the argument and the return at both the
+  // definition and the call.
+  cir.func @vbool24(%arg0: !cir.vector<24 x !cir.bool> {llvm.noundef}) -> 
(!cir.vector<24 x !cir.bool> {llvm.noundef}) {
+    cir.return %arg0 : !cir.vector<24 x !cir.bool>
+  }
+
+  // CHECK: cir.func{{.*}} @vbool24(%arg0: !u32i) -> !u32i
+
+  cir.func @call_vbool24(%arg0: !cir.vector<24 x !cir.bool>) -> !cir.vector<24 
x !cir.bool> {
+    %0 = cir.call @vbool24(%arg0) : (!cir.vector<24 x !cir.bool> 
{llvm.noundef}) -> (!cir.vector<24 x !cir.bool> {llvm.noundef})
+    cir.return %0 : !cir.vector<24 x !cir.bool>
+  }
+
+  // CHECK: cir.func{{.*}} @call_vbool24(%arg0: !u32i) -> !u32i
+  // CHECK: cir.call @vbool24(%{{.+}}) : (!u32i) -> !u32i
+
   cir.func @call_v128(%arg0: !cir.vector<4 x !cir.float>) -> !cir.vector<4 x 
!cir.float> {
     %0 = cir.call @v128(%arg0) : (!cir.vector<4 x !cir.float>) -> 
!cir.vector<4 x !cir.float>
     cir.return %0 : !cir.vector<4 x !cir.float>
@@ -123,3 +148,6 @@ module attributes {
 // LLVM: define void @take_farr(<2 x float> %{{[^,)]+}})
 // LLVM: define void @take_f3(<2 x float> %{{[^,)]+}}, float %{{[^,)]+}})
 // LLVM: define void @take_union_floats(<2 x float> %{{[^,)]+}})
+// LLVM: define noundef i8 @vbool4(i8 noundef %{{[^,)]+}})
+// LLVM: define i32 @vbool24(i32 %{{[^,)]+}})
+// LLVM: call i32 @vbool24(i32 %{{[^,)]+}})

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to