-
Notifications
You must be signed in to change notification settings - Fork 74
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
base: main
Are you sure you want to change the base?
Conversation
@superlopuh should this be dealt with by normalization? |
Codecov ReportAll modified and coverable lines are covered by tests ✅
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. |
This is a classic, I believe CC @math-fehr . I think this particular case will be dealt with with normalization |
It would be good to have a filecheck for this |
There was a problem hiding this 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?
It doesn't get converted to 0, it just adds an |
@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? |
Another possible solution is to add a truncating constructor to accept any size of input |
This is what I meant by having a wrapping IntegerAttr initialiser |
I would agree with the wrapping IntegerAttr init |
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this change?
There was a problem hiding this comment.
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
There was a problem hiding this 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?
I don't really see how this isn't composable into
I am also not sure I understand the concerns around signedness |
There was a problem hiding this 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?
I've realised I can probably replace the |
// 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
AdditionOfSameVariablesToMultiplyByTwo
has a corner case where the bitwidth is 1. This small change fixes this issue.