[CIR] Lower _BitInt to its in-memory storage integer (#206606)

ClangIR keeps a `_BitInt(N)` at its literal width, but in memory it has
to live in a wider padded integer, the same way `bool` is `i1` as a
value and `i8` in memory. Because CIRGen emitted the constant at its
exact width, the storage padding was left undefined: a `signed
_BitInt(6) = -1` wrote `0x3f` where classic CodeGen and GCC write
`0xff`. @andykaylor pointed this out on
[#205605](https://github.com/llvm/llvm-project/pull/205605).

The fix widens `_BitInt` to its storage integer `iM` and
sign/zero-extends by signedness during lowering to the LLVM dialect, the
same place `bool` becomes `i8` (`convertTypeForMemory` and the
to/from-memory helpers). CIRGen keeps the literal `!cir.int<N, bitint>`
unchanged.

The byte-array storage form for wide "split" widths, where the storage
integer's alloc size exceeds its store size (e.g. `_BitInt(129)` to
`i192`, 32 vs 24 bytes), is not implemented. The global, alloca, load,
and store lowerings report an NYI error and the aggregate type
converters fail the conversion, rather than emit a wrong-sized integer.
Atomic read-modify-write on a padded `_BitInt` and vector-of-`_BitInt`
are also unimplemented.

The storage width comes from `IntType::getABIAlignment`, which assumes a
fixed 64-bit maximum alignment (x86-64). It is verified byte-for-byte on
x86-64; on targets with a larger maximum (e.g. AArch64) any width that
would be mis-sized instead hits the split-storage NYI. On a target whose
`_BitInt` maximum alignment is below 64 bits (e.g. a 32-bit target) the
storage can be mis-sized. Making the computation target-aware needs the
target's `getBitIntAlign`, which is not in the data layout, and is left
as a follow-up.

The approach follows the RFC [Widen bool / _BitInt / ext-vector-bool to
their in-memory types in
lowering](https://discourse.llvm.org/t/rfc-clangir-widen-bool-bitint-ext-vector-bool-to-their-in-memory-types-in-lowering/91160).
It supersedes
[#205605](https://github.com/llvm/llvm-project/pull/205605), which
emitted the constant at its literal width.
This commit is contained in:
adams381 2026-07-21 13:20:00 -05:00 committed by GitHub
parent d81e22df45
commit 0aa8e45b04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 272 additions and 38 deletions

View file

@ -1309,9 +1309,10 @@ mlir::Attribute ConstantEmitter::emitForMemory(CIRGenModule &cgm,
cgm.errorNYI("emitForMemory: zero-extend HLSL bool vectors");
}
if (destType->isBitIntType()) {
cgm.errorNYI("emitForMemory: _BitInt type");
}
// CIR represents source types as literally as possible. Some types, such as
// bool and _BitInt(N), are kept at their literal width here and expanded to
// their wider "in memory" types during lowering to the LLVM dialect, so the
// constant is already in the right form and needs no adjustment.
return c;
}

View file

@ -76,6 +76,32 @@ mlir::Type elementTypeIfVector(mlir::Type type) {
}
} // namespace
/// In-memory storage width in bits for a _BitInt(N): N rounded up to the type's
/// ABI alignment. This equals sizeof(_BitInt(N)) * 8 on the default target
/// (e.g. _BitInt(6) -> 8, _BitInt(17) -> 32, _BitInt(128) -> 128).
static unsigned getBitIntMemoryStorageBits(cir::IntType ty,
const mlir::DataLayout &dataLayout) {
uint64_t alignBits = ty.getABIAlignment(dataLayout, {}) * 8;
return llvm::alignTo(ty.getWidth(), alignBits);
}
/// A _BitInt(N) whose padded storage integer iM has a larger alloc size than
/// its M/8 store size is laid out by clang as a byte array, not a plain integer
/// (e.g. _BitInt(129) -> i192 with alloc size 32 != store size 24). That
/// "split" storage form is not yet implemented; lowerings must detect it and
/// report errorNYI rather than emit the wrong-sized integer.
static bool isSplitStorageBitInt(cir::IntType ty,
const mlir::DataLayout &dataLayout) {
if (!ty.isBitInt())
return false;
unsigned storageBits = getBitIntMemoryStorageBits(ty, dataLayout);
auto storageTy = mlir::IntegerType::get(ty.getContext(), storageBits);
uint64_t storeSize = storageBits / 8;
uint64_t allocSize =
llvm::alignTo(storeSize, dataLayout.getTypeABIAlignment(storageTy));
return allocSize != storeSize;
}
/// Given a type convertor and a data layout, convert the given type to a type
/// that is suitable for memory operations. For example, this can be used to
/// lower cir.bool accesses to i8.
@ -89,9 +115,33 @@ static mlir::Type convertTypeForMemory(const mlir::TypeConverter &converter,
dataLayout.getTypeSizeInBits(type));
}
// _BitInt(N) keeps its literal width as a value but is stored in a padded
// integer iM in memory, the same way bool is i1 as a value and i8 in memory.
// The byte-array storage form for wide split widths is not implemented; a
// null return signals that, and op lowerings turn it into errorNYI.
if (auto intTy = mlir::dyn_cast<cir::IntType>(type);
intTy && intTy.isBitInt()) {
if (isSplitStorageBitInt(intTy, dataLayout))
return {};
return mlir::IntegerType::get(
type.getContext(), getBitIntMemoryStorageBits(intTy, dataLayout));
}
return converter.convertType(type);
}
/// Alignment to use for a memory access whose op carries no explicit alignment.
/// For _BitInt the storage integer iM's ABI alignment (e.g. i128's 16)
/// over-aligns the value, so use the CIR _BitInt ABI alignment (e.g. 8).
static uint64_t getMemoryFallbackAlignment(mlir::Type cirType,
mlir::Type llvmMemType,
const mlir::DataLayout &dataLayout) {
if (auto intTy = mlir::dyn_cast<cir::IntType>(cirType);
intTy && intTy.isBitInt())
return intTy.getABIAlignment(dataLayout, {});
return dataLayout.getTypeABIAlignment(llvmMemType);
}
static mlir::Value createIntCast(mlir::OpBuilder &bld, mlir::Value src,
mlir::IntegerType dstTy,
bool isSigned = false) {
@ -111,6 +161,23 @@ static mlir::Value createIntCast(mlir::OpBuilder &bld, mlir::Value src,
return mlir::LLVM::BitcastOp::create(bld, loc, dstTy, src);
}
/// Cast a _BitInt(N) value between its literal width iN and its padded
/// in-memory storage iM (sign/zero-extend to memory per signedness so the
/// padding bits are well-defined, truncate back on load; matches classic
/// CodeGen). Callers must first rule out the unsupported byte-array storage
/// form (isSplitStorageBitInt / a null convertTypeForMemory result).
static mlir::Value
castBitIntMemoryStorage(mlir::ConversionPatternRewriter &rewriter,
const mlir::DataLayout &dataLayout, cir::IntType intTy,
mlir::Value value, bool toMemory) {
unsigned storageBits = getBitIntMemoryStorageBits(intTy, dataLayout);
if (storageBits == intTy.getWidth())
return value;
unsigned dstBits = toMemory ? storageBits : intTy.getWidth();
return createIntCast(rewriter, value, rewriter.getIntegerType(dstBits),
/*isSigned=*/toMemory && intTy.isSigned());
}
static mlir::LLVM::Visibility
lowerCIRVisibilityToLLVMVisibility(cir::VisibilityKind visibilityKind) {
switch (visibilityKind) {
@ -137,6 +204,12 @@ static mlir::Value emitFromMemory(mlir::ConversionPatternRewriter &rewriter,
return createIntCast(rewriter, value, rewriter.getI1Type());
}
// Truncate the padded storage integer back to the _BitInt's literal width.
if (auto intTy = mlir::dyn_cast<cir::IntType>(op.getType());
intTy && intTy.isBitInt())
return castBitIntMemoryStorage(rewriter, dataLayout, intTy, value,
/*toMemory=*/false);
return value;
}
@ -155,6 +228,12 @@ static mlir::Value emitToMemory(mlir::ConversionPatternRewriter &rewriter,
return createIntCast(rewriter, value, memType);
}
// Sign/zero-extend the _BitInt value to its padded storage integer.
if (auto intTy = mlir::dyn_cast<cir::IntType>(origType);
intTy && intTy.isBitInt())
return castBitIntMemoryStorage(rewriter, dataLayout, intTy, value,
/*toMemory=*/true);
return value;
}
@ -443,9 +522,14 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::BoolAttr boolAttr) {
/// IntAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::IntAttr intAttr) {
mlir::Location loc = parentOp->getLoc();
return mlir::LLVM::ConstantOp::create(
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
// Materialize the value at its literal width, then widen to the in-memory
// storage type (a no-op except for _BitInt) so aggregate members built here
// match the iM struct/array fields produced by convertTypeForMemory.
mlir::Value val = mlir::LLVM::ConstantOp::create(
rewriter, loc, converter->convertType(intAttr.getType()),
intAttr.getValue());
return emitToMemory(rewriter, layout, intAttr.getType(), val);
}
/// FPAttr visitor.
@ -731,15 +815,19 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::TypeInfoAttr typeInfoAttr) {
/// UndefAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::UndefAttr undefAttr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
return mlir::LLVM::UndefOp::create(
rewriter, loc, converter->convertType(undefAttr.getType()));
rewriter, loc,
convertTypeForMemory(*converter, layout, undefAttr.getType()));
}
/// PoisonAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::PoisonAttr poisonAttr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
return mlir::LLVM::PoisonOp::create(
rewriter, loc, converter->convertType(poisonAttr.getType()));
rewriter, loc,
convertTypeForMemory(*converter, layout, poisonAttr.getType()));
}
// VTableAttr visitor.
@ -760,8 +848,9 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::VTableAttr vtableArr) {
/// ZeroAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ZeroAttr attr) {
mlir::Location loc = parentOp->getLoc();
return mlir::LLVM::ZeroOp::create(rewriter, loc,
converter->convertType(attr.getType()));
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
return mlir::LLVM::ZeroOp::create(
rewriter, loc, convertTypeForMemory(*converter, layout, attr.getType()));
}
// This class handles rewriting initializer attributes for types that do not
@ -780,7 +869,17 @@ public:
}
mlir::Attribute visitCirAttr(cir::IntAttr attr) {
return rewriter.getIntegerAttr(llvmType, attr.getValue());
// A _BitInt(N) global stores its value in a padded integer iM; sign/zero-
// extend the APInt to that width (a no-op for plain integers, whose value
// width already matches llvmType) so the IntegerAttr is well-typed.
llvm::APInt val = attr.getValue();
auto destTy = mlir::cast<mlir::IntegerType>(llvmType);
if (val.getBitWidth() != destTy.getWidth()) {
auto cirIntTy = mlir::cast<cir::IntType>(attr.getType());
val = cirIntTy.isSigned() ? val.sext(destTy.getWidth())
: val.zext(destTy.getWidth());
}
return rewriter.getIntegerAttr(llvmType, val);
}
mlir::Attribute visitCirAttr(cir::FPAttr attr) {
@ -1732,6 +1831,9 @@ mlir::LogicalResult CIRToLLVMAllocaOpLowering::matchAndRewrite(
typeConverter->convertType(rewriter.getIndexType()), 1);
mlir::Type elementTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getAllocaType());
if (!elementTy)
return op.emitError()
<< "NYI: lowering alloca of a type with no memory representation";
mlir::Type resultTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType());
@ -1938,10 +2040,13 @@ mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType());
if (!llvmTy)
return op.emitError()
<< "NYI: lowering load of a type with no memory representation";
mlir::LLVM::AtomicOrdering ordering = getLLVMMemOrder(op.getMemOrder());
std::optional<size_t> opAlign = op.getAlignment();
unsigned alignment =
(unsigned)opAlign.value_or(dataLayout.getTypeABIAlignment(llvmTy));
unsigned alignment = (unsigned)opAlign.value_or(
getMemoryFallbackAlignment(op.getType(), llvmTy, dataLayout));
assert(!cir::MissingFeatures::lowerModeOptLevel());
@ -1970,6 +2075,10 @@ cir::direct::CIRToLLVMVecMaskedLoadOpLowering::matchAndRewrite(
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmResTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType());
if (!llvmResTy)
return op.emitError()
<< "NYI: lowering masked load of a type with no memory "
"representation";
std::optional<size_t> opAlign = op.getAlignment();
unsigned alignment =
@ -1989,11 +2098,15 @@ mlir::LogicalResult CIRToLLVMStoreOpLowering::matchAndRewrite(
cir::StoreOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::LLVM::AtomicOrdering memorder = getLLVMMemOrder(op.getMemOrder());
mlir::Type valueType = op.getValue().getType();
const mlir::Type llvmTy =
getTypeConverter()->convertType(op.getValue().getType());
convertTypeForMemory(*getTypeConverter(), dataLayout, valueType);
if (!llvmTy)
return op.emitError()
<< "NYI: lowering store of a type with no memory representation";
std::optional<size_t> opAlign = op.getAlignment();
unsigned alignment =
(unsigned)opAlign.value_or(dataLayout.getTypeABIAlignment(llvmTy));
unsigned alignment = (unsigned)opAlign.value_or(
getMemoryFallbackAlignment(valueType, llvmTy, dataLayout));
assert(!cir::MissingFeatures::lowerModeOptLevel());
@ -2537,6 +2650,9 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
// This is the LLVM dialect type.
mlir::Type llvmType =
convertTypeForMemory(*getTypeConverter(), dataLayout, cirSymType);
if (!llvmType)
return op.emitError()
<< "NYI: lowering global of a type with no memory representation";
// A flexible array member initializer makes the constant larger than the
// record's declared type, so the global must use an oversized anonymous
@ -3327,9 +3443,19 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
converter.addConversion([&](cir::ArrayType type) -> mlir::Type {
mlir::Type ty =
convertTypeForMemory(converter, dataLayout, type.getElementType());
// A null element type means an unsupported member (e.g. a _BitInt with
// byte-array storage); propagate the conversion failure.
if (!ty)
return {};
return mlir::LLVM::LLVMArrayType::get(ty, type.getSize());
});
converter.addConversion([&](cir::VectorType type) -> mlir::Type {
// Vector-of-_BitInt memory layout is not modeled here (the element would
// stay at its literal width); report a conversion failure rather than emit
// an unvalidated vector.
if (auto intTy = mlir::dyn_cast<cir::IntType>(type.getElementType());
intTy && intTy.isBitInt())
return {};
const mlir::Type ty = converter.convertType(type.getElementType());
return mlir::VectorType::get(type.getSize(), ty, {type.getIsScalable()});
});
@ -3381,8 +3507,15 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
});
converter.addConversion([&](cir::StructType type) -> mlir::Type {
llvm::SmallVector<mlir::Type> llvmMembers;
for (mlir::Type ty : type.getMembers())
llvmMembers.push_back(convertTypeForMemory(converter, dataLayout, ty));
for (mlir::Type ty : type.getMembers()) {
mlir::Type memberTy = convertTypeForMemory(converter, dataLayout, ty);
// A null member means an unsupported type (e.g. a _BitInt with byte-array
// storage); propagate the conversion failure instead of building an
// invalid struct body.
if (!memberTy)
return {};
llvmMembers.push_back(memberTy);
}
mlir::LLVM::LLVMStructType llvmStruct;
if (type.getName()) {
@ -3400,11 +3533,19 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
converter.addConversion([&](cir::UnionType type) -> mlir::Type {
llvm::SmallVector<mlir::Type> llvmMembers;
if (!type.getMembers().empty())
if (auto storage = type.getUnionStorageType(dataLayout))
llvmMembers.push_back(
convertTypeForMemory(converter, dataLayout, storage));
if (mlir::Type pad = type.getPadding())
llvmMembers.push_back(convertTypeForMemory(converter, dataLayout, pad));
if (auto storage = type.getUnionStorageType(dataLayout)) {
mlir::Type storageTy =
convertTypeForMemory(converter, dataLayout, storage);
if (!storageTy)
return {};
llvmMembers.push_back(storageTy);
}
if (mlir::Type pad = type.getPadding()) {
mlir::Type padTy = convertTypeForMemory(converter, dataLayout, pad);
if (!padTy)
return {};
llvmMembers.push_back(padTy);
}
mlir::LLVM::LLVMStructType llvmStruct;
if (type.getName()) {

View file

@ -238,9 +238,15 @@ lowerConstArrayAttr(cir::ConstArrayAttr constArr,
if (mlir::isa<mlir::StringAttr>(constArr.getElts()))
return convertStringAttrToDenseElementsAttr(constArr,
converter->convertType(type));
if (mlir::isa<cir::IntType>(type))
if (mlir::isa<cir::IntType>(type)) {
// A _BitInt array element is stored in a padded integer in memory; the
// dense-attribute path here cannot express that widening, so fall back to
// the insertvalue region, which widens each element via emitToMemory.
if (mlir::cast<cir::IntType>(type).isBitInt())
return std::nullopt;
return convertToDenseElementsAttr<cir::IntAttr, mlir::APInt>(
constArr, dims, type, converter->convertType(type));
}
if (mlir::isa<cir::BoolType>(type))
return convertToDenseElementsAttr<cir::IntAttr, mlir::APInt>(
@ -299,9 +305,14 @@ lowerConstRecordMemberAttr(mlir::Attribute attr,
if (mlir::isa<cir::UndefAttr>(attr))
return mlir::LLVM::UndefAttr::get(ctx);
if (auto intAttr = mlir::dyn_cast<cir::IntAttr>(attr))
if (auto intAttr = mlir::dyn_cast<cir::IntAttr>(attr)) {
// A _BitInt member is stored in a padded integer in memory; defer to the
// insertvalue region (CIRAttrToValue), which performs the extension.
if (mlir::cast<cir::IntType>(intAttr.getType()).isBitInt())
return std::nullopt;
return mlir::IntegerAttr::get(converter->convertType(intAttr.getType()),
intAttr.getValue());
}
if (auto boolAttr = mlir::dyn_cast<cir::BoolAttr>(attr))
return mlir::IntegerAttr::get(converter->convertType(boolAttr.getType()),

View file

@ -534,11 +534,12 @@ void bitint_index_access(_BitInt(7) i) {
// CIR: cir.store{{.*}} %[[VAL]], %[[N_ADDR]] : !s32i, !cir.ptr<!s32i>
// LLVM: define{{.*}} @_Z19bitint_index_accessDB7_(i7 {{[^)]*}})
// LLVM: %[[I_ADDR:.*]] = alloca i7
// LLVM: %[[I_ADDR:.*]] = alloca i8
// LLVM: %[[ARR_ADDR:.*]] = alloca [10 x i32]
// LLVM: %[[N_ADDR:.*]] = alloca i32
// LLVM: %[[I:.*]] = load i7, ptr %[[I_ADDR]]
// LLVM: %[[IDX:.*]] = sext i7 %[[I]] to i64
// LLVM: %[[I:.*]] = load i8, ptr %[[I_ADDR]]
// LLVM: %[[I_CAST:.*]] = trunc i8 %[[I]] to i7
// LLVM: %[[IDX:.*]] = sext i7 %[[I_CAST]] to i64
// LLVM: %[[ELE:.*]] = getelementptr [10 x i32], ptr %[[ARR_ADDR]], i32 0, i64 %[[IDX]]
// LLVM: %[[VAL:.*]] = load i32, ptr %[[ELE]]
// LLVM: store i32 %[[VAL]], ptr %[[N_ADDR]]

View file

@ -0,0 +1,61 @@
// 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
signed _BitInt(6) s6_neg1 = -1;
unsigned _BitInt(6) u6_63 = 63;
signed _BitInt(6) s6_neg2 = -2;
signed _BitInt(2) s2_neg1 = -1;
unsigned _BitInt(4) u4_15 = 15;
signed _BitInt(17) s17_neg1 = -1;
unsigned _BitInt(17) u17_100 = 100;
signed _BitInt(128) s128 = 1234;
// CIR-DAG: cir.global external @s6_neg1 = #cir.int<-1> : !cir.int<s, 6, bitint> {alignment = 1 : i64}
// CIR-DAG: cir.global external @u6_63 = #cir.int<63> : !cir.int<u, 6, bitint> {alignment = 1 : i64}
// CIR-DAG: cir.global external @s6_neg2 = #cir.int<-2> : !cir.int<s, 6, bitint> {alignment = 1 : i64}
// CIR-DAG: cir.global external @s2_neg1 = #cir.int<-1> : !cir.int<s, 2, bitint> {alignment = 1 : i64}
// CIR-DAG: cir.global external @u4_15 = #cir.int<15> : !cir.int<u, 4, bitint> {alignment = 1 : i64}
// CIR-DAG: cir.global external @s17_neg1 = #cir.int<-1> : !cir.int<s, 17, bitint> {alignment = 4 : i64}
// CIR-DAG: cir.global external @u17_100 = #cir.int<100> : !cir.int<u, 17, bitint> {alignment = 4 : i64}
// CIR-DAG: cir.global external @s128 = #cir.int<1234> : !s128i_bitint {alignment = 8 : i64}
// A signed _BitInt is sign-extended across its padded storage integer; an
// unsigned one is zero-extended. Storage width is the ABI size (i6 -> i8,
// i17 -> i32, i128 stays i128 with align 8).
// LLVM-DAG: @s6_neg1 = global i8 -1, align 1
// LLVM-DAG: @u6_63 = global i8 63, align 1
// LLVM-DAG: @s6_neg2 = global i8 -2, align 1
// LLVM-DAG: @s2_neg1 = global i8 -1, align 1
// LLVM-DAG: @u4_15 = global i8 15, align 1
// LLVM-DAG: @s17_neg1 = global i32 -1, align 4
// LLVM-DAG: @u17_100 = global i32 100, align 4
// LLVM-DAG: @s128 = global i128 1234, align 8
struct S { _BitInt(17) m; };
struct S gs = {-1};
// CIR-DAG: cir.global external @gs = #cir.const_record<{#cir.int<-1> : !cir.int<s, 17, bitint>}> : !rec_S {alignment = 4 : i64}
// LLVM-DAG: @gs = global %struct.S { i32 -1 }, align 4
signed _BitInt(17) garr[3] = {-1, 2, -3};
// CIR-DAG: cir.global external @garr = #cir.const_array<[#cir.int<-1> : !cir.int<s, 17, bitint>, #cir.int<2> : !cir.int<s, 17, bitint>, #cir.int<-3> : !cir.int<s, 17, bitint>]> : !cir.array<!cir.int<s, 17, bitint> x 3> {alignment = 4 : i64}
// LLVM-DAG: @garr = global [3 x i32] [i32 -1, i32 2, i32 -3], align 4
void store_load(signed _BitInt(17) *p, signed _BitInt(17) v) { *p = v; }
// CIR-LABEL: cir.func {{.*}} @store_load
// CIR: %[[V:.+]] = cir.load {{.*}} : !cir.ptr<!cir.int<s, 17, bitint>>, !cir.int<s, 17, bitint>
// CIR: cir.store {{.*}} %[[V]], {{.*}} : !cir.int<s, 17, bitint>, !cir.ptr<!cir.int<s, 17, bitint>>
// The value is sign-extended on store into i32 memory and truncated back to
// i17 on load.
// LLVM-LABEL: @store_load
// LLVM: %[[EXT:.+]] = sext i17 %{{.+}} to i32
// LLVM: store i32 %[[EXT]], ptr %{{.+}}, align 4
// LLVM: %[[LD:.+]] = load i32, ptr %{{.+}}, align 4
// LLVM: %[[TR:.+]] = trunc i32 %[[LD]] to i17

View file

@ -0,0 +1,27 @@
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DGLOBAL %s -o - 2>&1 | FileCheck %s --check-prefix=GLOBAL
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DALLOCA %s -o - 2>&1 | FileCheck %s --check-prefix=ALLOCA
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DSTORE %s -o - 2>&1 | FileCheck %s --check-prefix=STORE
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -DLOAD %s -o - 2>&1 | FileCheck %s --check-prefix=LOAD
#ifdef GLOBAL
signed _BitInt(129) g129 = 1;
// GLOBAL: NYI: lowering global of a type with no memory representation
#endif
#ifdef ALLOCA
signed _BitInt(129) use_local(signed _BitInt(129) a) {
signed _BitInt(129) x = a;
return x;
}
// ALLOCA: NYI: lowering alloca of a type with no memory representation
#endif
#ifdef STORE
void store_lit(signed _BitInt(129) *p) { *p = (signed _BitInt(129))1; }
// STORE: NYI: lowering store of a type with no memory representation
#endif
#ifdef LOAD
int load_cmp(signed _BitInt(129) *p) { return *p != 0; }
// LOAD: NYI: lowering load of a type with no memory representation
#endif

View file

@ -20,25 +20,22 @@ signed _BitInt(128) bitint128_var;
__int128 int128_var;
signed _BitInt(256) bitint256_var;
signed _BitInt(254) bitint254_var;
signed _BitInt(257) bitint257_var;
// CIR: cir.global external @bitint128_var = #cir.int<0> : !s128i_bitint {alignment = 8 : i64}
// CIR: cir.global external @int128_var = #cir.int<0> : !s128i {alignment = 16 : i64}
// CIR: cir.global external @bitint256_var = #cir.int<0> : !s256i_bitint
// CIR: cir.global external @bitint254_var = #cir.int<0> : !cir.int<s, 254, bitint>
// CIR: cir.global external @bitint257_var = #cir.int<0> : !cir.int<s, 257, bitint>
// _BitInt is stored in its padded storage integer in memory (e.g. i254 -> i256).
// LLVM: @bitint128_var = global i128 0, align 8
// LLVM: @int128_var = global i128 0, align 16
// LLVM: @bitint256_var = global i256 0
// LLVM: @bitint254_var = global i254 0, align 8
// LLVM: @bitint257_var = global i257 0, align 8
// LLVM: @bitint254_var = global i256 0, align 8
// OGCG: @bitint128_var = global i128 0, align 8
// OGCG: @int128_var = global i128 0, align 16
// OGCG: @bitint256_var = global i256 0
// OGCG: @bitint254_var = global i256 0, align 8
// OGCG: @bitint257_var = global [40 x i8] zeroinitializer, align 8
void take_bitint_32(_BitInt(32) x) {}
// CIR: cir.func {{.*}} @take_bitint_32(%arg0: !s32i_bitint
@ -60,11 +57,6 @@ void take_bitint_254(signed _BitInt(254) x) {}
// LLVM: define {{.*}} void @take_bitint_254(i254 {{.*}})
// OGCG: define {{.*}} void @take_bitint_254(ptr noundef byval(i256) align 8 {{.*}})
void take_bitint_257(signed _BitInt(257) x) {}
// CIR: cir.func {{.*}} @take_bitint_257(%arg0: !cir.int<s, 257, bitint>
// LLVM: define {{.*}} void @take_bitint_257(i257 {{.*}})
// OGCG: define {{.*}} void @take_bitint_257(ptr noundef byval([40 x i8]) align 8 {{.*}})
// Regular __int128 should NOT have the bitint flag.
void take_int128(__int128 x) {}
// CIR: cir.func {{.*}} @take_int128(%arg0: !s128i

View file

@ -76,11 +76,11 @@ char32_t c32;
// OGCG: @c32 = global i32 0, align 4
_BitInt(20) sb20;
// CHECK: @sb20 = global i20 0, align 4
// CHECK: @sb20 = global i32 0, align 4
// OGCG: @sb20 = global i32 0, align 4
unsigned _BitInt(48) ub48;
// CHECK: @ub48 = global i48 0, align 8
// CHECK: @ub48 = global i64 0, align 8
// OGCG: @ub48 = global i64 0, align 8
bool boolfalse = false;