[mlir][IR] DenseElementsAttr: Remove i1 dense packing special case (#180397)
`DenseElementsAttr` stores elements in a `ArrayRef<char>` buffer, where each element is padded to a full byte. Before this commit, there used to be a special storage format for `i1` elements: they used to be densely packed, i.e., 1 bit per element. This commit removes the dense packing special case for `i1`. This commit removes complexity from `DenseElementsAttr`. If dense packing is needed in the future it could be implemented in a general way that works for all element types (based on #179122). Discussion: https://discourse.llvm.org/t/denseelementsattr-i1-element-type/62525
This commit is contained in:
parent
0776af16c5
commit
c6964b1b4d
9 changed files with 25 additions and 283 deletions
|
|
@ -442,18 +442,6 @@ private:
|
|||
const std::optional<std::vector<int64_t>> &explicitShape,
|
||||
MlirContext &context);
|
||||
|
||||
// There is a complication for boolean numpy arrays, as numpy represents
|
||||
// them as 8 bits (1 byte) per boolean, whereas MLIR bitpacks them into 8
|
||||
// booleans per byte.
|
||||
static MlirAttribute getBitpackedAttributeFromBooleanBuffer(
|
||||
Py_buffer &view, std::optional<std::vector<int64_t>> explicitShape,
|
||||
MlirContext &context);
|
||||
|
||||
// This does the opposite transformation of
|
||||
// `getBitpackedAttributeFromBooleanBuffer`
|
||||
std::unique_ptr<nb_buffer_info>
|
||||
getBooleanBufferFromBitpackedAttribute() const;
|
||||
|
||||
template <typename Type>
|
||||
std::unique_ptr<nb_buffer_info>
|
||||
bufferInfo(MlirType shapedType, const char *explicitFormat = nullptr) {
|
||||
|
|
|
|||
|
|
@ -199,11 +199,7 @@ public:
|
|||
///
|
||||
/// The format of the raw buffer is a densely packed array of values that
|
||||
/// can be bitcast to the storage format of the element type specified.
|
||||
/// Types that are not byte aligned will be:
|
||||
/// - For bitwidth > 1: Rounded up to the next byte.
|
||||
/// - For bitwidth = 1: Packed into 8bit bytes with bits corresponding to
|
||||
/// the linear order of the shape type from MSB to LSB, padded to on the
|
||||
/// right.
|
||||
/// Types that are not byte aligned will be rounded up to the next byte.
|
||||
static DenseElementsAttr getFromRawBuffer(ShapedType type,
|
||||
ArrayRef<char> rawBuffer);
|
||||
|
||||
|
|
|
|||
|
|
@ -45,16 +45,12 @@ For conversions outside of these types, a `type=` must be explicitly provided
|
|||
and the buffer contents must be bit-castable to the MLIR internal
|
||||
representation:
|
||||
|
||||
* Integer types (except for i1): the buffer must be byte aligned to the
|
||||
next byte boundary.
|
||||
* Integer types: the buffer must be byte aligned to the next byte boundary.
|
||||
* Floating point types: Must be bit-castable to the given floating point
|
||||
size.
|
||||
* i1 (bool): Bit packed into 8bit words where the bit pattern matches a
|
||||
row major ordering. An arbitrary Numpy `bool_` array can be bit packed to
|
||||
this specification with: `np.packbits(ary, axis=None, bitorder='little')`.
|
||||
* i1 (bool): Each boolean value is stored as a single byte (0 or 1).
|
||||
|
||||
If a single element buffer is passed (or for i1, a single byte with value 0
|
||||
or 255), then a splat will be created.
|
||||
If a single element buffer is passed, then a splat will be created.
|
||||
|
||||
Args:
|
||||
array: The array or buffer to convert.
|
||||
|
|
@ -63,8 +59,7 @@ Args:
|
|||
type: Skips inference of the MLIR element type and uses this instead. The
|
||||
storage size must be consistent with the actual contents of the buffer.
|
||||
shape: Overrides the shape of the buffer when constructing the MLIR
|
||||
shaped type. This is needed when the physical and logical shape differ (as
|
||||
for i1).
|
||||
shaped type. This is needed when the physical and logical shape differ.
|
||||
context: Explicit context, if not from context manager.
|
||||
|
||||
Returns:
|
||||
|
|
@ -122,14 +117,6 @@ Raises:
|
|||
type or if the buffer does not meet expectations.
|
||||
)";
|
||||
|
||||
/// Local helper checking if the current machine is little endian.
|
||||
static bool isLittleEndian() {
|
||||
const uint16_t value = 1;
|
||||
unsigned char first_byte = 0;
|
||||
std::memcpy(&first_byte, &value, 1);
|
||||
return first_byte == 1;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// Local helper adapted from llvm::scope_exit.
|
||||
template <typename Callable>
|
||||
|
|
@ -775,10 +762,7 @@ std::unique_ptr<nb_buffer_info> PyDenseElementsAttribute::accessBuffer() {
|
|||
} else if (mlirTypeIsAInteger(elementType) &&
|
||||
mlirIntegerTypeGetWidth(elementType) == 1) {
|
||||
// i1 / bool
|
||||
// We can not send the buffer directly back to Python, because the i1
|
||||
// values are bitpacked within MLIR. We call numpy's unpackbits function
|
||||
// to convert the bytes.
|
||||
return getBooleanBufferFromBitpackedAttribute();
|
||||
return bufferInfo<bool>(shapedType);
|
||||
}
|
||||
|
||||
// TODO: Currently crashes the program.
|
||||
|
|
@ -892,9 +876,7 @@ MlirAttribute PyDenseElementsAttribute::getAttributeFromBuffer(
|
|||
bulkLoadElementType = mlirF16TypeGet(context);
|
||||
} else if (format == "?") {
|
||||
// i1
|
||||
// The i1 type needs to be bit-packed, so we will handle it separately
|
||||
return getBitpackedAttributeFromBooleanBuffer(view, explicitShape,
|
||||
context);
|
||||
bulkLoadElementType = mlirIntegerTypeGet(context, 1);
|
||||
} else if (isSignedIntegerFormat(format)) {
|
||||
if (view.itemsize == 4) {
|
||||
// i32
|
||||
|
|
@ -946,82 +928,6 @@ MlirAttribute PyDenseElementsAttribute::getAttributeFromBuffer(
|
|||
return mlirDenseElementsAttrRawBufferGet(type, view.len, view.buf);
|
||||
}
|
||||
|
||||
MlirAttribute PyDenseElementsAttribute::getBitpackedAttributeFromBooleanBuffer(
|
||||
Py_buffer &view, std::optional<std::vector<int64_t>> explicitShape,
|
||||
MlirContext &context) {
|
||||
if (!isLittleEndian()) {
|
||||
// Given we have no good way of testing the behavior on big-endian
|
||||
// systems we will throw
|
||||
throw nb::type_error("Constructing a bit-packed MLIR attribute is "
|
||||
"unsupported on big-endian systems");
|
||||
}
|
||||
nb::ndarray<uint8_t, nb::numpy, nb::ndim<1>, nb::c_contig> unpackedArray(
|
||||
/*data=*/static_cast<uint8_t *>(view.buf),
|
||||
/*shape=*/{static_cast<size_t>(view.len)});
|
||||
|
||||
nb::module_ numpy = nb::module_::import_("numpy");
|
||||
nb::object packbitsFunc = numpy.attr("packbits");
|
||||
nb::object packedBooleans =
|
||||
packbitsFunc(nb::cast(unpackedArray), "bitorder"_a = "little");
|
||||
nb_buffer_info pythonBuffer = nb::cast<nb_buffer>(packedBooleans).request();
|
||||
|
||||
MlirType bitpackedType = getShapedType(mlirIntegerTypeGet(context, 1),
|
||||
std::move(explicitShape), view);
|
||||
assert(pythonBuffer.itemsize == 1 && "Packbits must return uint8");
|
||||
// Notice that `mlirDenseElementsAttrRawBufferGet` copies the memory of
|
||||
// packedBooleans, hence the MlirAttribute will remain valid even when
|
||||
// packedBooleans get reclaimed by the end of the function.
|
||||
return mlirDenseElementsAttrRawBufferGet(bitpackedType, pythonBuffer.size,
|
||||
pythonBuffer.ptr);
|
||||
}
|
||||
|
||||
std::unique_ptr<nb_buffer_info>
|
||||
PyDenseElementsAttribute::getBooleanBufferFromBitpackedAttribute() const {
|
||||
if (!isLittleEndian()) {
|
||||
// Given we have no good way of testing the behavior on big-endian
|
||||
// systems we will throw
|
||||
throw nb::type_error("Constructing a numpy array from a MLIR attribute "
|
||||
"is unsupported on big-endian systems");
|
||||
}
|
||||
|
||||
int64_t numBooleans = mlirElementsAttrGetNumElements(*this);
|
||||
int64_t numBitpackedBytes = (numBooleans + 7) / 8;
|
||||
uint8_t *bitpackedData = static_cast<uint8_t *>(
|
||||
const_cast<void *>(mlirDenseElementsAttrGetRawData(*this)));
|
||||
nb::ndarray<uint8_t, nb::numpy, nb::ndim<1>, nb::c_contig> packedArray(
|
||||
/*data=*/bitpackedData,
|
||||
/*shape=*/{static_cast<size_t>(numBitpackedBytes)});
|
||||
|
||||
nb::module_ numpy = nb::module_::import_("numpy");
|
||||
nb::object unpackbitsFunc = numpy.attr("unpackbits");
|
||||
nb::object equalFunc = numpy.attr("equal");
|
||||
nb::object reshapeFunc = numpy.attr("reshape");
|
||||
nb::object unpackedBooleans =
|
||||
unpackbitsFunc(nb::cast(packedArray), "bitorder"_a = "little");
|
||||
|
||||
// Unpackbits operates on bytes and gives back a flat 0 / 1 integer array.
|
||||
// We need to:
|
||||
// 1. Slice away the padded bits
|
||||
// 2. Make the boolean array have the correct shape
|
||||
// 3. Convert the array to a boolean array
|
||||
unpackedBooleans = unpackedBooleans[nb::slice(
|
||||
nb::int_(0), nb::int_(numBooleans), nb::int_(1))];
|
||||
unpackedBooleans = equalFunc(unpackedBooleans, 1);
|
||||
|
||||
MlirType shapedType = mlirAttributeGetType(*this);
|
||||
intptr_t rank = mlirShapedTypeGetRank(shapedType);
|
||||
std::vector<intptr_t> shape(rank);
|
||||
for (intptr_t i = 0; i < rank; ++i) {
|
||||
shape[i] = mlirShapedTypeGetDimSize(shapedType, i);
|
||||
}
|
||||
unpackedBooleans = reshapeFunc(unpackedBooleans, shape);
|
||||
|
||||
// Make sure the returned nb::buffer_view claims ownership of the data
|
||||
// in `pythonBuffer` so it remains valid when Python reads it
|
||||
nb_buffer pythonBuffer = nb::cast<nb_buffer>(unpackedBooleans);
|
||||
return std::make_unique<nb_buffer_info>(pythonBuffer.request());
|
||||
}
|
||||
|
||||
PyType_Slot PyDenseElementsAttribute::slots[] = {
|
||||
// Python 3.8 doesn't allow setting the buffer protocol slots from a type spec.
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
|
|
|
|||
|
|
@ -90,10 +90,7 @@ struct DenseIntOrFPElementsAttrStorage : public DenseElementsAttributeStorage {
|
|||
|
||||
// If the data is already known to be a splat, the key hash value is
|
||||
// directly the data buffer.
|
||||
bool isBoolData = ty.getElementType().isInteger(1);
|
||||
if (isKnownSplat) {
|
||||
if (isBoolData)
|
||||
return getKeyForSplatBoolData(ty, data[0] != 0);
|
||||
return KeyTy(ty, data, llvm::hash_value(data), isKnownSplat);
|
||||
}
|
||||
|
||||
|
|
@ -103,12 +100,8 @@ struct DenseIntOrFPElementsAttrStorage : public DenseElementsAttributeStorage {
|
|||
size_t numElements = ty.getNumElements();
|
||||
assert(numElements != 1 && "splat of 1 element should already be detected");
|
||||
|
||||
// Handle boolean values directly as they are packed to 1-bit.
|
||||
if (isBoolData)
|
||||
return getKeyForBoolData(ty, data, numElements);
|
||||
|
||||
size_t elementWidth = getDenseElementBitWidth(ty.getElementType());
|
||||
// Non 1-bit dense elements are padded to 8-bits.
|
||||
// Dense elements are padded to 8-bits.
|
||||
size_t storageSize = llvm::divideCeil(elementWidth, CHAR_BIT);
|
||||
assert(((data.size() / storageSize) == numElements) &&
|
||||
"data does not hold expected number of elements");
|
||||
|
|
@ -127,45 +120,6 @@ struct DenseIntOrFPElementsAttrStorage : public DenseElementsAttributeStorage {
|
|||
return KeyTy(ty, firstElt, hashVal, /*isSplat=*/true);
|
||||
}
|
||||
|
||||
/// Construct a key with a set of boolean data.
|
||||
static KeyTy getKeyForBoolData(ShapedType ty, ArrayRef<char> data,
|
||||
size_t numElements) {
|
||||
ArrayRef<char> splatData = data;
|
||||
bool splatValue = splatData.front() & 1;
|
||||
|
||||
// Check the simple case where the data matches the known splat value.
|
||||
if (splatData == ArrayRef<char>(splatValue ? kSplatTrue : kSplatFalse))
|
||||
return getKeyForSplatBoolData(ty, splatValue);
|
||||
|
||||
// Handle the case where the potential splat value is 1 and the number of
|
||||
// elements is non 8-bit aligned.
|
||||
size_t numOddElements = numElements % CHAR_BIT;
|
||||
if (splatValue && numOddElements != 0) {
|
||||
// Check that all bits are set in the last value.
|
||||
char lastElt = splatData.back();
|
||||
if (lastElt != llvm::maskTrailingOnes<unsigned char>(numOddElements))
|
||||
return KeyTy(ty, data, llvm::hash_value(data));
|
||||
|
||||
// If this is the only element, the data is known to be a splat.
|
||||
if (splatData.size() == 1)
|
||||
return getKeyForSplatBoolData(ty, splatValue);
|
||||
splatData = splatData.drop_back();
|
||||
}
|
||||
|
||||
// Check that the data buffer corresponds to a splat of the proper mask.
|
||||
char mask = splatValue ? ~0 : 0;
|
||||
return llvm::all_of(splatData, [mask](char c) { return c == mask; })
|
||||
? getKeyForSplatBoolData(ty, splatValue)
|
||||
: KeyTy(ty, data, llvm::hash_value(data));
|
||||
}
|
||||
|
||||
/// Return a key to use for a boolean splat of the given value.
|
||||
static KeyTy getKeyForSplatBoolData(ShapedType type, bool splatValue) {
|
||||
const char &splatData = splatValue ? kSplatTrue : kSplatFalse;
|
||||
return KeyTy(type, splatData, llvm::hash_value(splatData),
|
||||
/*isSplat=*/true);
|
||||
}
|
||||
|
||||
/// Hash the key for the storage.
|
||||
static llvm::hash_code hashKey(const KeyTy &key) {
|
||||
return llvm::hash_combine(key.type, key.hashCode);
|
||||
|
|
@ -189,13 +143,6 @@ struct DenseIntOrFPElementsAttrStorage : public DenseElementsAttributeStorage {
|
|||
}
|
||||
|
||||
ArrayRef<char> data;
|
||||
|
||||
/// The values used to denote a boolean splat value.
|
||||
// This is not using constexpr declaration due to compilation failure
|
||||
// encountered with MSVC where it would inline these values, which makes it
|
||||
// unsafe to refer by reference in KeyTy.
|
||||
static const char kSplatTrue;
|
||||
static const char kSplatFalse;
|
||||
};
|
||||
|
||||
/// An attribute representing a reference to a dense vector or tensor object
|
||||
|
|
|
|||
|
|
@ -453,31 +453,15 @@ LogicalResult OpaqueAttr::verify(function_ref<InFlightDiagnostic()> emitError,
|
|||
// DenseElementsAttr Utilities
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
const char DenseIntOrFPElementsAttrStorage::kSplatTrue = ~0;
|
||||
const char DenseIntOrFPElementsAttrStorage::kSplatFalse = 0;
|
||||
|
||||
/// Get the bitwidth of a dense element type within the buffer.
|
||||
/// DenseElementsAttr requires bitwidths greater than 1 to be aligned by 8.
|
||||
/// DenseElementsAttr requires bitwidths to be aligned by 8.
|
||||
static size_t getDenseElementStorageWidth(size_t origWidth) {
|
||||
return origWidth == 1 ? origWidth : llvm::alignTo<8>(origWidth);
|
||||
return llvm::alignTo<8>(origWidth);
|
||||
}
|
||||
static size_t getDenseElementStorageWidth(Type elementType) {
|
||||
return getDenseElementStorageWidth(getDenseElementBitWidth(elementType));
|
||||
}
|
||||
|
||||
/// Set a bit to a specific value.
|
||||
static void setBit(char *rawData, size_t bitPos, bool value) {
|
||||
if (value)
|
||||
rawData[bitPos / CHAR_BIT] |= (1 << (bitPos % CHAR_BIT));
|
||||
else
|
||||
rawData[bitPos / CHAR_BIT] &= ~(1 << (bitPos % CHAR_BIT));
|
||||
}
|
||||
|
||||
/// Return the value of the specified bit.
|
||||
static bool getBit(const char *rawData, size_t bitPos) {
|
||||
return (rawData[bitPos / CHAR_BIT] & (1 << (bitPos % CHAR_BIT))) != 0;
|
||||
}
|
||||
|
||||
/// Copy actual `numBytes` data from `value` (APInt) to char array(`result`) for
|
||||
/// BE format.
|
||||
static void copyAPIntToArrayForBEmachine(APInt value, size_t numBytes,
|
||||
|
|
@ -546,11 +530,7 @@ static void copyArrayToAPIntForBEmachine(const char *inArray, size_t numBytes,
|
|||
static void writeBits(char *rawData, size_t bitPos, APInt value) {
|
||||
size_t bitWidth = value.getBitWidth();
|
||||
|
||||
// If the bitwidth is 1 we just toggle the specific bit.
|
||||
if (bitWidth == 1)
|
||||
return setBit(rawData, bitPos, value.isOne());
|
||||
|
||||
// Otherwise, the bit position is guaranteed to be byte aligned.
|
||||
// The bit position is guaranteed to be byte aligned.
|
||||
assert((bitPos % CHAR_BIT) == 0 && "expected bitPos to be 8-bit aligned");
|
||||
if (llvm::endianness::native == llvm::endianness::big) {
|
||||
// Copy from `value` to `rawData + (bitPos / CHAR_BIT)`.
|
||||
|
|
@ -570,11 +550,7 @@ static void writeBits(char *rawData, size_t bitPos, APInt value) {
|
|||
/// Reads the next `bitWidth` bits from the bit position `bitPos` in array
|
||||
/// `rawData`.
|
||||
static APInt readBits(const char *rawData, size_t bitPos, size_t bitWidth) {
|
||||
// Handle a boolean bit position.
|
||||
if (bitWidth == 1)
|
||||
return APInt(1, getBit(rawData, bitPos) ? 1 : 0);
|
||||
|
||||
// Otherwise, the bit position must be 8-bit aligned.
|
||||
// The bit position is guaranteed to be byte aligned.
|
||||
assert((bitPos % CHAR_BIT) == 0 && "expected bitPos to be 8-bit aligned");
|
||||
APInt result(bitWidth, 0);
|
||||
if (llvm::endianness::native == llvm::endianness::big) {
|
||||
|
|
@ -664,7 +640,7 @@ DenseElementsAttr::BoolElementIterator::BoolElementIterator(
|
|||
attr.getRawData().data(), attr.isSplat(), dataIndex) {}
|
||||
|
||||
bool DenseElementsAttr::BoolElementIterator::operator*() const {
|
||||
return getBit(getData(), getDataIndex());
|
||||
return static_cast<bool>(getData()[getDataIndex()]);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
@ -910,7 +886,6 @@ bool DenseElementsAttr::classof(Attribute attr) {
|
|||
DenseElementsAttr DenseElementsAttr::get(ShapedType type,
|
||||
ArrayRef<Attribute> values) {
|
||||
assert(hasSameNumElementsOrSplat(type, values));
|
||||
|
||||
Type eltType = type.getElementType();
|
||||
|
||||
// Take care complex type case first.
|
||||
|
|
@ -984,10 +959,6 @@ DenseElementsAttr DenseElementsAttr::get(ShapedType type,
|
|||
writeBits(data.data(), i * storageBitWidth, intVal);
|
||||
}
|
||||
|
||||
// Handle the special encoding of splat of bool.
|
||||
if (values.size() == 1 && eltType.isInteger(1))
|
||||
data[0] = data[0] ? -1 : 0;
|
||||
|
||||
return DenseIntOrFPElementsAttr::getRaw(type, data);
|
||||
}
|
||||
|
||||
|
|
@ -995,25 +966,9 @@ DenseElementsAttr DenseElementsAttr::get(ShapedType type,
|
|||
ArrayRef<bool> values) {
|
||||
assert(hasSameNumElementsOrSplat(type, values));
|
||||
assert(type.getElementType().isInteger(1));
|
||||
|
||||
SmallVector<char> buff(llvm::divideCeil(values.size(), CHAR_BIT));
|
||||
|
||||
if (!values.empty()) {
|
||||
bool isSplat = true;
|
||||
bool firstValue = values[0];
|
||||
for (int i = 0, e = values.size(); i != e; ++i) {
|
||||
isSplat &= values[i] == firstValue;
|
||||
setBit(buff.data(), i, values[i]);
|
||||
}
|
||||
|
||||
// Splat of bool is encoded as a byte with all-ones in it.
|
||||
if (isSplat) {
|
||||
buff.resize(1);
|
||||
buff[0] = values[0] ? -1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return DenseIntOrFPElementsAttr::getRaw(type, buff);
|
||||
return DenseIntOrFPElementsAttr::getRaw(
|
||||
type, ArrayRef<char>(reinterpret_cast<const char *>(values.data()),
|
||||
values.size()));
|
||||
}
|
||||
|
||||
DenseElementsAttr DenseElementsAttr::get(ShapedType type,
|
||||
|
|
@ -1084,23 +1039,7 @@ bool DenseElementsAttr::isValidRawBuffer(ShapedType type,
|
|||
// The initializer is always a splat if the result type has a single element.
|
||||
detectedSplat = numElements == 1;
|
||||
|
||||
// Storage width of 1 is special as it is packed by the bit.
|
||||
if (storageWidth == 1) {
|
||||
// Check for a splat, or a buffer equal to the number of elements which
|
||||
// consists of either all 0's or all 1's.
|
||||
if (rawBuffer.size() == 1) {
|
||||
auto rawByte = static_cast<uint8_t>(rawBuffer[0]);
|
||||
if (rawByte == 0 || rawByte == 0xff) {
|
||||
detectedSplat = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// This is a valid non-splat buffer if it has the right size.
|
||||
return rawBufferWidth == llvm::alignTo<8>(numElements);
|
||||
}
|
||||
|
||||
// All other types are 8-bit aligned, so we can just check the buffer width
|
||||
// All types are 8-bit aligned, so we can just check the buffer width
|
||||
// to know if only a single initializer element was passed in.
|
||||
if (rawBufferWidth == storageWidth) {
|
||||
detectedSplat = true;
|
||||
|
|
@ -1319,10 +1258,6 @@ static void writeAPIntsToBuffer(size_t storageWidth,
|
|||
assert((*it).getBitWidth() <= storageWidth);
|
||||
writeBits(data.data(), offset, *it);
|
||||
}
|
||||
|
||||
// Handle the special encoding of splat of a boolean.
|
||||
if (numValues == 1 && (*values.begin()).getBitWidth() == 1)
|
||||
data[0] = data[0] ? -1 : 0;
|
||||
}
|
||||
|
||||
/// Constructs a dense elements attribute from an array of raw APFloat values.
|
||||
|
|
@ -1480,12 +1415,7 @@ static ShapedType mappingHelper(Fn mapping, Attr &attr, ShapedType inType,
|
|||
|
||||
// Check for the splat case.
|
||||
if (attr.isSplat()) {
|
||||
if (bitWidth == 1) {
|
||||
// Handle the special encoding of splat of bool.
|
||||
data[0] = mapping(*attr.begin()).isZero() ? 0 : -1;
|
||||
} else {
|
||||
processElt(*attr.begin(), /*index=*/0);
|
||||
}
|
||||
processElt(*attr.begin(), /*index=*/0);
|
||||
return newArrayType;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// RUN: mlir-opt -canonicalize %s | mlir-opt | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: @large_i1_tensor_roundtrip
|
||||
func.func @large_i1_tensor_roundtrip() -> tensor<160xi1> {
|
||||
%cst_0 = arith.constant dense<"0xFFF00000FF000000FF000000FF000000FF000000"> : tensor<160xi1>
|
||||
%cst_1 = arith.constant dense<"0xFF000000FF000000FF000000FF000000FF0000F0"> : tensor<160xi1>
|
||||
// CHECK: dense<"0xFF000000FF000000FF000000FF000000FF000000">
|
||||
%0 = arith.andi %cst_0, %cst_1 : tensor<160xi1>
|
||||
return %0 : tensor<160xi1>
|
||||
}
|
||||
|
|
@ -36,8 +36,8 @@ func.func @parse_i4_tensor() -> tensor<32xi4> {
|
|||
}
|
||||
|
||||
// CHECK-LABEL: @parse_i1_tensor
|
||||
func.func @parse_i1_tensor() -> tensor<256xi1> {
|
||||
// CHECK: dense<"0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F"> : tensor<256xi1>
|
||||
%0 = arith.constant dense<"0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F"> : tensor<256xi1>
|
||||
return %0 : tensor<256xi1>
|
||||
func.func @parse_i1_tensor() -> tensor<32xi1> {
|
||||
// CHECK: dense<[true, false, true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, true]> : tensor<32xi1>
|
||||
%0 = arith.constant dense<"0x0100010001010101010101010101010101010101010101010100000000000001"> : tensor<32xi1>
|
||||
return %0 : tensor<32xi1>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,11 +258,10 @@ def testGetDenseElementsInteger4():
|
|||
@run
|
||||
def testGetDenseElementsBool():
|
||||
with Context():
|
||||
bool_array = np.array([[1, 0, 1], [0, 1, 0]], dtype=np.bool_)
|
||||
array = np.packbits(bool_array, axis=None, bitorder="little")
|
||||
attr = DenseElementsAttr.get(
|
||||
array, type=IntegerType.get_signless(1), shape=bool_array.shape
|
||||
bool_array = np.array(
|
||||
[[True, False, True], [False, True, False]], dtype=np.bool_
|
||||
)
|
||||
attr = DenseElementsAttr.get(bool_array)
|
||||
# CHECK: dense<{{\[}}[true, false, true], [false, true, false]]> : tensor<2x3xi1>
|
||||
print(attr)
|
||||
|
||||
|
|
|
|||
|
|
@ -76,20 +76,6 @@ TEST(DenseSplatTest, BoolSplatRawRoundtrip) {
|
|||
EXPECT_EQ(trueSplat, trueSplatFromRaw);
|
||||
}
|
||||
|
||||
TEST(DenseSplatTest, BoolSplatSmall) {
|
||||
MLIRContext context;
|
||||
Builder builder(&context);
|
||||
|
||||
// Check that splats that don't fill entire byte are handled properly.
|
||||
auto tensorType = RankedTensorType::get({4}, builder.getI1Type());
|
||||
std::vector<char> data{0b00001111};
|
||||
auto trueSplatFromRaw =
|
||||
DenseIntOrFPElementsAttr::getFromRawBuffer(tensorType, data);
|
||||
EXPECT_TRUE(trueSplatFromRaw.isSplat());
|
||||
DenseElementsAttr trueSplat = DenseElementsAttr::get(tensorType, true);
|
||||
EXPECT_EQ(trueSplat, trueSplatFromRaw);
|
||||
}
|
||||
|
||||
TEST(DenseSplatTest, LargeBoolSplat) {
|
||||
constexpr int64_t boolCount = 56;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue