-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
[stdlib] Optimize normalize_index for unsigned types #3957
base: main
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
As a side note: fn main():
# binary operators use the method of left side type
print(Int(-1) < UInt(1<<63)) # False
print(UInt(1<<63) > Int(-1)) # False
print(UInt(1) > Int(-1)) # False
print(Int(-1) < UInt(1)) # True |
This comment has been minimized.
This comment has been minimized.
3cbdfbb
to
08417d9
Compare
I've found out that by doing the bounds check after the index normalization we can avoid a comparison This is also True for SIMD so if needed we can add support UInt8.MAX sized containers and use negative Int8 indexes without upcasting or doing more expensive bounds check, the only cost is the normalization which can be done branchless. |
8878984
to
2581aa0
Compare
|
||
@parameter | ||
if ( | ||
_type_is_eq[IdxType, UInt]() |
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.
This long chain of type comparisons makes me thing we want an UnsignedInteger
trait. However, I'd like to see a bounds check that UInt
is large enough, ideally a fast path at compile time using .MAX
and a slower path at runtime. UInt
is large enough for all values right now, so this is a forward-looking change for UInt128 and UInt256 (which cryptography wants).
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.
added a compile time check for sizeof index <= sizeof length.
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.
removed the check since it is wrong, since a big size struct can implement the Indexer trait and return a single property as the index.
By the definition of the current indexer trait it has to be convertible to mlir_index which represent the hardware width integer, so currently we can't really have UInt128 index in 64bit hardware.
) | ||
return i | ||
else: | ||
var i = UInt(index(idx)) |
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.
This cast should remove values < 0, so the next check is always true.
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.
This does a bitcast to control where we do Signed/Unsigned comparison in the following code.
On the hardware it uses base 2 complement, so addition is the same for signed and unsigned, but comparison is different. It does work, see the unit tests.
I added an intermediate mlir_index to make it more clear.
ContainerType: Sized, //, container_name: StringLiteral | ||
](idx: Int, container: ContainerType) -> Int: | ||
IdxType: Indexer, //, container_name: StringLiteral | ||
](idx: IdxType, length: Int) -> Int: |
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.
length
should probably be a UInt
in most cases. I can't think of many reasons to have it be and using Int
for length gets rid of a lot of address space.
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.
So currently there is an implicit cast between Int and UInt and you have to be very careful when mixing them (see example in the comment above for what can go wrong)
The rational for adding another variant is to make working with UInt or Int a decision for the user, the returned index type will be the same as the user container length type.
This open the possibility for SIMD sized container e.g. UInt8 sized container should get back UInt8 type index.
Since currently all stdlib containers are Int sized, we need the Int variant.
3d86089
to
d12ef3d
Compare
Use the Indexer trait in normalize_index to optimize for UInt, UInt8, UInt16, UInt32, and UInt64 types. Signed-off-by: Yinon Burgansky <[email protected]>
d12ef3d
to
230ff56
Compare
Use the
Indexer
trait innormalize_index
to optimize forUInt
,UInt8
,UInt16
,UInt32
, andUInt64
types.