This is an archive of the discontinued LLVM Phabricator instance.

[mlir] Fix printer when it is a DenseElementsAttr of i1
ClosedPublic

Authored by yaochengji on Apr 1 2022, 11:14 AM.

Details

Summary

A large DenseElementsAttr of i1could trigger a bug in printer/parser roundtrip.

Ex. A DenseElementsAttr of i1 with 200 elements will print as Hex format of length 400 before the fix. However, when parsing the printed text, an error will be triggered. After fix, the printed length will be 50.

Diff Detail

Event Timeline

yaochengji created this revision.Apr 1 2022, 11:14 AM
Herald added a project: Restricted Project. · View Herald Transcript
yaochengji requested review of this revision.Apr 1 2022, 11:14 AM

Can you add a test case?

Can you add a test case?

My current test case uses mhlo dialect. I could put it in comment first.

test.mlir
func @main() -> tensor<10x32xi1> {
    %20 = mhlo.constant dense<true> : tensor<i1>
    %21 = "mhlo.broadcast_in_dim"(%20) {broadcast_dimensions = dense<> : tensor<0xi64>} : (tensor<i1>) -> tensor<10x8xi1>
    %22 = mhlo.constant dense<false> : tensor<i1>
    %23 = "mhlo.pad"(%21, %22) {edge_padding_high = dense<[0, 24]> : tensor<2xi64>, edge_padding_low = dense<0> : tensor<2xi64>, interior_padding = dense<0> : tensor<2xi64>} : (tensor<10x8xi1>, tensor<i1>) -> tensor<10x32xi1>
    return %23 : tensor<10x32xi1>
}

mlir-hlo-opt test.mlir -canonicalize | mlir-hlo-opt and then it will fail.

Can you add a test case?

My current test case uses mhlo dialect. I could put it in comment first.

test.mlir
func @main() -> tensor<10x32xi1> {
    %20 = mhlo.constant dense<true> : tensor<i1>
    %21 = "mhlo.broadcast_in_dim"(%20) {broadcast_dimensions = dense<> : tensor<0xi64>} : (tensor<i1>) -> tensor<10x8xi1>
    %22 = mhlo.constant dense<false> : tensor<i1>
    %23 = "mhlo.pad"(%21, %22) {edge_padding_high = dense<[0, 24]> : tensor<2xi64>, edge_padding_low = dense<0> : tensor<2xi64>, interior_padding = dense<0> : tensor<2xi64>} : (tensor<10x8xi1>, tensor<i1>) -> tensor<10x32xi1>
    return %23 : tensor<10x32xi1>
}

mlir-hlo-opt test.mlir -canonicalize | mlir-hlo-opt and then it will fail.

We'll want to have a test that we can add to the MLIR directly (i.e. not dependent on HLO) so that we can make sure the fix works/we don't regress.

You should be able to test this with a module with an arith.constant

Even just an empty module with an appropriate attribute?

yaochengji updated this revision to Diff 419902.Apr 1 2022, 6:39 PM
yaochengji updated this revision to Diff 419904.Apr 1 2022, 6:45 PM

Thanks everyone for your suggestion. I've added a new test case.

Even just an empty module with an appropriate attribute?

A simple attribute is not enough. A new large i1 attribute needs to be created to trigger the bug. In the test I used a canonicalization pass to produce the new arith.constant with i1 attr.

@rriddle I've added the test case, could you take a look at it?

rriddle accepted this revision.Apr 11 2022, 10:12 AM

Can you a bit more detail to the description describing what the underlying problem was?

This revision is now accepted and ready to land.Apr 11 2022, 10:12 AM
yaochengji added a comment.EditedApr 11 2022, 10:21 AM

Can you a bit more detail to the description describing what the underlying problem was?

A large DenseElementsAttr of i1could trigger a bug in printer/parser roundtrip.

Ex. A DenseElementsAttr of i1 with 200 elements will print as Hex format of length 400 before the fix. However, when parsing the printed text, an error will be triggered. After fix, the printed length will be 50.

Can you a bit more detail to the description describing what the underlying problem was?

A large DenseElementsAttr of i1could trigger a bug in printer/parser roundtrip.

Ex. A DenseElementsAttr of i1 with 200 elements will print as Hex format of length 100 before the fix. However, when parsing the printed text, an error will be triggered.

Yes, I mean please add this to the commit description. Right now it's blank.

yaochengji edited the summary of this revision. (Show Details)Apr 11 2022, 10:30 AM

@rriddle , it's added, thanks.

Hi @rriddle , could it be merged?

There seems no responds from @rriddle .

@antiagainst Could you take a look at this?

What email do you want this commit to be attributed to?

What email do you want this commit to be attributed to?

Thanks for your reply. My email is yaochengji101@gmail.com

This revision was automatically updated to reflect the committed changes.

Looks like this new test case fails on s390x, causing the build bot to go red:
https://lab.llvm.org/buildbot/#/builders/199/builds/5139

Looks like this new test case fails on s390x, causing the build bot to go red:
https://lab.llvm.org/buildbot/#/builders/199/builds/5139

It seems the value after const folding is wrong, it should be dense<"0xFF000000FF000000FF000000FF000000FF000000">, not a splat const dense<false>. But this test runs successfully on my machine.

It seems the value after const folding is wrong, it should be dense<"0xFF000000FF000000FF000000FF000000FF000000">, not a splat const dense<false>.

It looks to me like the error occurs even earlier, in the parser. Running without -canonicalize I already see:

[uweigand@a35lp68 mlir]$ /home/uweigand/llvm/build/mlir/bin/mlir-opt /home/uweigand/llvm/llvm-head/mlir/test/IR/attribute-roundtrip.mlir 
module {
  func.func @large_i1_tensor_roundtrip() -> tensor<160xi1> {
    %cst = arith.constant dense<false> : tensor<160xi1>
    %cst_0 = arith.constant dense<false> : tensor<160xi1>
    %0 = arith.andi %cst, %cst_0 : tensor<160xi1>
    return %0 : tensor<160xi1>
  }
}

Debugging this further, the root cause seems to be that when TensorLiteralParser::getHexAttr tries to perform an endian conversion here:

if (llvm::support::endian::system_endianness() ==
    llvm::support::endianness::big) {
  // Convert endianess in big-endian(BE) machines. `rawData` is
  // little-endian(LE) because HEX in raw data of dense element attribute
  // is always LE format. It is converted into BE here to be used in BE
  // machines.
  SmallVector<char, 64> outDataVec(rawData.size());
  MutableArrayRef<char> convRawData(outDataVec);
  DenseIntOrFPElementsAttr::convertEndianOfArrayRefForBEmachine(
      rawData, convRawData, type);
  return DenseElementsAttr::getFromRawBuffer(type, convRawData,
                                             detectedSplat);
}

the resulting convRawData remains all zeroes. This in turn seems to be because DenseIntOrFPElementsAttr::convertEndianOfArrayRefForBEmachine does not handle sub-byte types correctly.

convertEndianOfArrayRefForBEmachine calls convertEndianOfCharForBEmachine with an elementBitWidth of 1, which falls into this default case:

default: {
  size_t nBytes = elementBitWidth / CHAR_BIT;
  for (size_t i = 0; i < nBytes; i++)
    std::copy_n(inRawData + (nBytes - 1 - i), 1, outRawData + i);
  break;
}

If elementBitWidth is smaller than CHAR_BIT, that simply does nothing and leaves outRawData unmodified.

Not sure what the correct fix is. Maybe it would be as simple as skipping the endian conversion for types not larger than a single byte?

Indeed, the following patch fixes the problem for me:

diff --git a/mlir/lib/IR/BuiltinAttributes.cpp b/mlir/lib/IR/BuiltinAttributes.cpp
index 0004fe90fe87..d887fafc2059 100644
--- a/mlir/lib/IR/BuiltinAttributes.cpp
+++ b/mlir/lib/IR/BuiltinAttributes.cpp
@@ -1157,8 +1157,11 @@ void DenseIntOrFPElementsAttr::convertEndianOfArrayRefForBEmachine(
   size_t elementBitWidth = getDenseElementStorageWidth(elementType);
   assert(numElements * elementBitWidth == inRawData.size() * CHAR_BIT &&
          inRawData.size() <= outRawData.size());
-  convertEndianOfCharForBEmachine(inRawData.begin(), outRawData.begin(),
-                                  elementBitWidth, numElements);
+  if (elementBitWidth <= CHAR_BIT)
+    std::memcpy(outRawData.begin(), inRawData.begin(), inRawData.size());
+  else
+    convertEndianOfCharForBEmachine(inRawData.begin(), outRawData.begin(),
+                                    elementBitWidth, numElements);
 }
 
 //===----------------------------------------------------------------------===//

Not sure if this is really the right place, though.