Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dialects: (builtin) add IntegerAttr truncation and use in individual rewrite #3585

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

alexarice
Copy link
Collaborator

AdditionOfSameVariablesToMultiplyByTwo has a corner case where the bitwidth is 1. This small change fixes this issue.

@alexarice alexarice added bug Something isn't working minor For minor PRs, easy and quick to review, quickly mergeable transformations Changes or adds a transformatio labels Dec 6, 2024
@alexarice alexarice self-assigned this Dec 6, 2024
@alexarice
Copy link
Collaborator Author

@superlopuh should this be dealt with by normalization?

Copy link

codecov bot commented Dec 6, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.43%. Comparing base (b5acf31) to head (123638a).
Report is 27 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3585      +/-   ##
==========================================
+ Coverage   90.40%   90.43%   +0.02%     
==========================================
  Files         469      472       +3     
  Lines       58950    59301     +351     
  Branches     5611     5639      +28     
==========================================
+ Hits        53296    53629     +333     
- Misses       4212     4228      +16     
- Partials     1442     1444       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@superlopuh
Copy link
Member

This is a classic, I believe CC @math-fehr . I think this particular case will be dealt with with normalization

@superlopuh
Copy link
Member

It would be good to have a filecheck for this

Copy link
Collaborator

@math-fehr math-fehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question, why was the previous implementation wrong?
With an i1, the constant 2 should be folded to 0 right?
So x + x gets folded to x * 0, which is correct AFAIK?

@alexarice
Copy link
Collaborator Author

Question, why was the previous implementation wrong? With an i1, the constant 2 should be folded to 0 right? So x + x gets folded to x * 0, which is correct AFAIK?

It doesn't get converted to 0, it just adds an i1 with 2 inside which then fails a verify check

@alexarice
Copy link
Collaborator Author

@math-fehr @superlopuh any idea how to proceed here? Would it be better to always wrap integers to their range, or at least have an IntegerAttr initialiser that does this?

@superlopuh
Copy link
Member

Another possible solution is to add a truncating constructor to accept any size of input

@alexarice
Copy link
Collaborator Author

This is what I meant by having a wrapping IntegerAttr initialiser

@math-fehr
Copy link
Collaborator

I would agree with the wrapping IntegerAttr init

@alexarice
Copy link
Collaborator Author

I've pushed a version which adds an option to truncate an IntegerAttr. If this seems reasonable I can potentially split that into a separate PR or at least add some unit tests.


if self.signedness.data == Signedness.SIGNLESS:
if self.signedness.data != Signedness.UNSIGNED:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing the bitwise and seems to wrap everything into the range [0,2**bitwidth), so for the signed case we need to normalise it back to the correct range. This seemed like the easiest way to do this but it could be refactored

Copy link
Member

@superlopuh superlopuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that these are entirely composable things, and signedness complicates things. I'm not sure how to best do this in the general case, handling all possible signednesses and scenarios. Would it make sense to change this PR to only handle the i1 case in the pattern, and then start a separate discussion on how to truncate integers?

@alexarice
Copy link
Collaborator Author

I don't really see how this isn't composable into

  1. Add optional truncation for IntegerAttr (with added tests)
  2. Enable the flag on this test.

I am also not sure I understand the concerns around signedness

Copy link
Member

@superlopuh superlopuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I played around with the code locally, and I get how it works a bit better now, happy to go with this direction. Could you please add unit tests for the helper, and a filechec test for the canonicalization pattern?

@alexarice
Copy link
Collaborator Author

I've realised I can probably replace the & (2**8 - 1) with % 2**8, which acts exactly the same as far as I can tell and is way more readable

@alexarice alexarice changed the title transforms: (individual-rewrite) fix i1 case dialects: (builtin) add IntegerAttr truncation and use in individual rewrite Dec 10, 2024
@alexarice alexarice requested a review from superlopuh December 10, 2024 09:13
Comment on lines 4 to 18
// CHECK: %[[#var:]] = "test.op"() : () -> i32
// CHECK-NEXT: %[[#two:]] = arith.constant 2 : i32
// CHECK-NEXT: %{{.*}} = arith.muli %[[#var]], %[[#two]] : i32

%0 = "test.op"() : () -> (i32)
%1 = arith.addi %0, %0 : i32

// -----

// CHECK: %[[#var:]] = "test.op"() : () -> i1
// CHECK-NEXT: %[[#zero:]] = arith.constant false
// CHECK-NEXT: %{{.*}} = arith.muli %[[#var]], %[[#zero]] : i1

%0 = "test.op"() : () -> (i1)
%1 = arith.addi %0, %0 : i1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: are the regexes here really more readable than giving names to ssa values and checking for those directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed 1, but can't/haven't named the constants

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you name the inputs, the constants they are replaced with inherit their hints, but this is more of a nit at this point

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want the test to be checking that the constant has the same name as the original operation, and so would prefer to use the regex

Copy link
Collaborator

@math-fehr math-fehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Looks good to me!

@@ -397,21 +397,26 @@ def verify_value(self, value: int):
f"values in the range [{min_value}, {max_value})"
)

def normalized_value(self, value: IntAttr) -> IntAttr | None:
def normalized_value(
self, value: IntAttr, *, truncate_bits: bool = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a name like wrapping would be clearer than truncate_bits.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what's it wrapping?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping the int into the range. For what it's worth the overflow flags use the word wrap

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and truncating has already a meaning in LLVM/arith, which is to go from let's say i32 to i16, which is not really what we are doing here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But wrapping is a feature of the operation, not of a value normalization step. I'm happy for an addition to be wrapping, but it seems a bit out of context here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the relevant API that I have most experience with https://developer.apple.com/documentation/swift/int/init(truncating:)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we come to a conclusion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can hash it out on the zulip if you want, I'm happy to be outvoted if you both think wrapping is more intuitive here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working minor For minor PRs, easy and quick to review, quickly mergeable transformations Changes or adds a transformatio
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants