[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:
parent
d81e22df45
commit
0aa8e45b04
8 changed files with 272 additions and 38 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
|
|||
61
clang/test/CIR/CodeGen/bitint-memory.c
Normal file
61
clang/test/CIR/CodeGen/bitint-memory.c
Normal 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
|
||||
27
clang/test/CIR/CodeGen/bitint-split-storage-nyi.c
Normal file
27
clang/test/CIR/CodeGen/bitint-split-storage-nyi.c
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue