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

[stdlib] SIMD conformance to EqualityComparable #2412

Open
wants to merge 1 commit into
base: nightly
Choose a base branch
from

Conversation

helehex
Copy link
Contributor

@helehex helehex commented Apr 26, 2024

This allows SIMD to conform to EqualityComparable, without losing any of the original behavior.

It uses the 4th overload resolution rule to give the new methods lower precedence, while still conforming to EqualityComparable

@helehex helehex requested a review from a team as a code owner April 26, 2024 00:58
Copy link
Contributor

@gabrieldemarmiesse gabrieldemarmiesse left a comment

Choose a reason for hiding this comment

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

I wouldn't be against some more details (in the docs or something) since this seems a bit like black magic and using return types for overload resolution :)
Maybe the docstring would be a good place to explain how to actually use this overload?

@helehex
Copy link
Contributor Author

helehex commented Apr 26, 2024

Yeah that might be good. I'm not even sure this is something that would get pulled because of that, but figured i'd give it a shot since it's just a few lines and gives expected behavior

Copy link
Collaborator

@JoeLoser JoeLoser left a comment

Choose a reason for hiding this comment

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

This is a pretty neat way to make SIMD conform to EqualityComparable, props to your cleverness!

This seems like something we should do in the short-term until we have conditional conformance and other things. Accepting this PR would unlock a bunch of other future nice things. To start with, we could then push this EqualityComparable requirement into CollectionElement, which would then mean we can now implement __eq__ and __ne__ for collections. Further, it would allow us to simplify things like List.count and List.index which currently use an ad-hoc composite trait to ensure the given type implements __eq__ since that isn’t a requirement of CollectionElement.

I've added this PR to bring up with the team on Monday during our internal design discussions, and I'll circle back after we meet. But this is super cool, thanks for pushing on this!


assert_equal(s1 == s1, SIMD[DType.bool, 2](True, True), "s1 == s1")
assert_equal(s1 != s1, SIMD[DType.bool, 2](False, False), "s1 != s1")
assert_equal(eq(s1, s1), True, "eq(s1, s1)")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion Use assert_true in this line and assert_false in the line below rather than assert_equal with True and False, respectively? The intent will be clearer IMO. Ditto elsewhere in this file where that pattern shows up.

E.g.

assert_true(eq(s1, s1), "eq(s1, s1)")

or even drop eq and friends entirely in favor of:

assert_true(s1 == s1, "s1 == s1")

What do you think? Is your intent of the existence of eq and ne to ensure SIMD conforms to EqualityComparable trait, or something else? If so, it's not needed as the compiler will ensure SIMD conforms to the trait since you explicitly said SIMD shall conform to it in its trait list. If it didn't implement the necessary requirements, mojo would issue a hard error at compile time.

Copy link
Contributor Author

@helehex helehex Apr 27, 2024

Choose a reason for hiding this comment

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

The first should work, but i think the second one might cause issues.
however i should be able to do assert_equal(s1, s1) and assert_not_equal(s1, s2)

The downside of using this trick:
the behavior of != changes slightly, depending if you're in the context of EqualityComparable
(because __bool__ will always use reduce_and), so i was using the ne function to ensure that.

I'll try it out in a lil bit though

@helehex
Copy link
Contributor Author

helehex commented Apr 28, 2024

sora pointed out that this would not be confluent under generalization, and we agreed that for now, SIMD.__bool__() should be constrained to size=1, so that if the logic of bool(a != b) changed out from under you, it at least gives a compiler error

@helehex
Copy link
Contributor Author

helehex commented Apr 28, 2024

we also agree that being explicit about reduce_and/reduce_or makes the most sense.
maybe something like specifying parameters on self would be nice here, to see the error in the editor:

fn __bool__(self: SIMD[type, 1]) -> Bool:
    ...

@helehex
Copy link
Contributor Author

helehex commented Apr 29, 2024

ok this feel like the right way to go

  • Added some simd helper functions: _all_equal, _any_equal, _all_not_equal, _any_not_equal.
  • Changed every case i could find of implicit reduce functions to be explicit. (tests pass but may have missed some)
  • SIMD.__bool__() constrained to size=1
  • Removed some things that are unnecessary now, like overloads for assert_equal and assert_not_equal
  • Tweaked some tests to make more sense
  • Fixed a pre-existing bug with _isclose() while changing to explicit reduce

constrain SIMD.__bool__() to size=1
change to explicit reduce for SIMD

Signed-off-by: Max Brylski <helehex@gmail.com>
@soraros
Copy link
Contributor

soraros commented Apr 30, 2024

You might need to coordinate with @leandrolcampos a bit because of #2375.

@helehex
Copy link
Contributor Author

helehex commented Apr 30, 2024

I'm still not sure this should or will get pulled. It feels good writing simd code like this. You get a compiler error when converting a SIMD with size > 1 to Bool, such as with if statements, but it doesn't show up in the editor. I can see that being confusing. maybe it's still better than what we have currently.

@soraros
Copy link
Contributor

soraros commented Apr 30, 2024

@helehex IMHO, the foot gun mitigation probably already made it worth it.

@helehex
Copy link
Contributor Author

helehex commented Apr 30, 2024

another trade-off that should be mentioned:
Ideally, everything should be boolable, and this breaks that
but like sora mentioned, when working with simd size > 1, the boolability causes trouble

@JoeLoser
Copy link
Collaborator

JoeLoser commented May 3, 2024

Thanks for pushing on this! I think this is a general direction we want to go with, but there's a couple things going on in this PR I'd recommend we decompose/split up into separate PRs. Specifically, I'd recommend

  1. Constrain __bool__ to ensure size is 1 to avoid this footgun. I'd recommend beefing up the constrained message to be something like
constrained[width == 1, "The truth value of a SIMD vector with more than one element is ambiguous. Use `v.reduce_and()` or `v.reduce_or()`"]()
  1. I'd like to see a proposal of just changing SIMD.eq to return a Bool instead of being cute with the overload rules. I think this is a better long-term approach for generic programming and the footgun we have now with SIMD equality. I'd recommend we reference things like Swift and Rust when considering this API change. Take a look at https://github.com/apple/swift-evolution/blob/main/proposals/0229-simd.md#detailed-design and how Rust models its simd_eq and friends here. In addition, I'd recommend we also discuss how we want to model the other comparison operators. This may change in the library once we have extensions in the language too.

  2. Split up the constructor removal into standalone PR? I don't think it needs to be coupled with these changes, right?

@abduld I'm also curious to get your general thoughts here as I know I've brought up my concerns with the existing SIMD.eq in the past.

@helehex
Copy link
Contributor Author

helehex commented May 4, 2024

everyone i've talked to likes how simd.eq returns a simd though

@helehex
Copy link
Contributor Author

helehex commented May 4, 2024

I agree we need a proper proposal though.
For now i'll just open a separate pr for constraining simd.__bool__. That will also align better with simd.__int__

@helehex
Copy link
Contributor Author

helehex commented May 4, 2024

removing the bool constructor wasn't necessary, but it helped weed out all the cases of implicit reduce and is also very much related

@helehex
Copy link
Contributor Author

helehex commented May 4, 2024

it finally occurred to me that reduce_and has bitwise ambiguity

@ematejska ematejska added the mojo-repo Tag all issues with this label label May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mojo-repo Tag all issues with this label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants