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

very slow derived PartialEq/PartialOrd implementation of enum #31886

Closed
oli-obk opened this issue Feb 25, 2016 · 5 comments
Closed

very slow derived PartialEq/PartialOrd implementation of enum #31886

oli-obk opened this issue Feb 25, 2016 · 5 comments
Labels
I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness

Comments

@oli-obk
Copy link
Contributor

oli-obk commented Feb 25, 2016

The following code goes into an infinite loop or at least takes more than 10 seconds without optimizations. With optimizations it runs and the assertion fails. If anything but 0 and 0x8000_0000_0000_0000 are chosen for the discriminants, the code finishes instantly even in debug mode. Playground

#[derive(PartialEq, PartialOrd)]
enum Eu64 {
    Au64 = 0,
    Bu64 = 0x8000_0000_0000_0000
}

fn main() {
    assert!(Eu64::Bu64 > Eu64::Au64);
}

There's something odd going on anyway, as the discriminant is inferred to be isize instead of u64 (This unit test seems to suggest it should be u64)

cc #24290 @pnkfelix

@durka
Copy link
Contributor

durka commented Feb 25, 2016

Expanded:

enum Eu64 { Au64 = 0, Bu64 = 9223372036854775808, }

impl ::std::cmp::PartialOrd for Eu64 {
    #[inline]
    fn partial_cmp(&self, __arg_0: &Eu64)
     -> ::std::option::Option<::std::cmp::Ordering> {
        /* elided */
    }
    #[inline]
    fn lt(&self, __arg_0: &Eu64) -> bool {
        /* elided */
    }
    #[inline]
    fn le(&self, __arg_0: &Eu64) -> bool {
        /* elided */
    }
    #[inline]
    fn gt(&self, __arg_0: &Eu64) -> bool {
        {
            let __self_vi =
                unsafe { ::std::intrinsics::discriminant_value(&*self) } as
                    i32;
            let __arg_1_vi =
                unsafe { ::std::intrinsics::discriminant_value(&*__arg_0) } as
                    i32;
            if true && __self_vi == __arg_1_vi {
                match (&*self, &*__arg_0) {
                    (&Eu64::Au64, &Eu64::Au64) => false,
                    (&Eu64::Bu64, &Eu64::Bu64) => false,
                    _ => unsafe { ::std::intrinsics::unreachable() }
                }
            } else { __self_vi.gt(&__arg_1_vi) }
        }
    }
    #[inline]
    fn ge(&self, __arg_0: &Eu64) -> bool {
        /* elided */
    }
}

impl ::std::cmp::PartialEq for Eu64 {
    /* elided */
}

Red flags:

  • the discriminant is casted to i32, rather than isize or even u64!
  • the logic is confusing. If the (truncated) discriminants are equal, it ignores them and checks the variants. The generated code would presumably make more sense if there were data in the variants.

Other than that, I don't see what's causing the slowdown. (edit: was wrong) The full expanded code exhibits the same behavior.

With the cast to i32 in mind, the assertion failure is expected. It looks like the deriving code looks for a #[repr] attribute and otherwise defaults to i32. This makes some sense since derivings have only syntactic information... but not a lot of sense.

(edit again) It has to cast in case the repr is signed, because otherwise the comparison will be backwards. But I don't see how to get it right, because at deriving time there isn't enough information to infer the repr (might have to look up consts, etc). Maybe if we redesign the discriminant_value intrinsic like this:

trait DiscriminantValue<T> {
    type Discriminant: Eq + Ord;
    fn value(&self) -> Self::Discriminant;
}

@durka
Copy link
Contributor

durka commented Feb 25, 2016

In a debugger, it is always stuck on the line (of the expanded source):

80                          (&Eu64::Bu64, &Eu64::Bu64) => false,

Disassembly shows it stuck on this instruction:

=> 0x0000555555558b39 <+169>:   jmp    0x555555558b39 <oli_exp::Eu64.::std::cmp::PartialOrd::gt+169>

Well then... I don't know x86 assembly but that looks like an infinite loop to me! I dumped the assembly from rustc and found this gem:

.LBB7_6:
.LBB7_7:
        jmp     .LBB7_6

So it seems there is a codegen bug in addition to the discriminant type issue.

@durka
Copy link
Contributor

durka commented Feb 25, 2016

Oh, suddenly it makes sense. LBB7_6 (or 7) must correspond to the catchall arm of the match, which is unreachable. It's unreachable because the discriminants compared equal, but we already know that's a false positive caused by integer truncation. So it triggers undefined behavior.

@durka
Copy link
Contributor

durka commented Feb 25, 2016

Talked with @talchas on IRC. Turns out if you give a value for an enum variant, it has to be the same type as the given #[repr], or isize if no attribute. Therefore, perhaps the only change necessary is to change the default cast from i32 to isize in the derived code.

@talchas
Copy link

talchas commented Feb 25, 2016

Obnoxiously

enum Foo { A = 0, X = 0x8000_0000_0000_0000 }
fn main() {
    println!("{} {}", Foo::X as u64, unsafe { std::intrinsics::discriminant_value(&Foo::X) });
}

both compiles and prints "0 9223372036854775808" on 32-bit (same with as i64 and equivalents with using 0x1000... instead of 0x8000). And of course Foo is 8 bytes wide (it's even tested!).

So values that don't fit in an i32 give silently crazy behavior on 32-bit in other places.

@steveklabnik steveklabnik added I-wrong I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness labels Feb 25, 2016
durka added a commit to durka/rust that referenced this issue Mar 10, 2016
It was originally intended to be i32, but it isn't.

Fixes rust-lang#31886.
durka added a commit to durka/rust that referenced this issue Mar 10, 2016
It was originally intended to be i32, but it isn't.

Fixes rust-lang#31886.
Manishearth added a commit to Manishearth/rust that referenced this issue Mar 12, 2016
cleanups and fixes for #[derive]

This contains a bunch of little cleanups and fixes to `#[derive]`. There are more extensive comments on each individual commit.

- hygiene cleanups
- use `discriminant_value` instead of variant index in `#[derive(Hash)]`
- ~~don't move out of borrowed content in `#[derive(PartialOrd, PartialEq)]`~~
- use `intrinsics::unreachable()` instead of `unreachable!()`

I don't believe there are any breaking changes in here, but I do want some more eyes on that.

Fixes rust-lang#2810 (!), I believe (we still assume that "std" or "core" is the standard library but so does the rest of rustc...).
Fixes rust-lang#21714 (cc @apasel422).
~~Fixes~~ (postponed) rust-lang#24047 (cc @withoutboats @bluss).
Fixes rust-lang#31714 (cc @alexcrichton @bluss).
Fixes rust-lang#31886 (cc @oli-obk).
durka added a commit to durka/rust that referenced this issue Mar 13, 2016
It was originally intended to be i32, but it isn't.

Fixes rust-lang#31886.
bors added a commit that referenced this issue Mar 13, 2016
cleanups and fixes for #[derive]

This contains a bunch of little cleanups and fixes to `#[derive]`. There are more extensive comments on each individual commit.

- hygiene cleanups
- use `discriminant_value` instead of variant index in `#[derive(Hash)]`
- ~~don't move out of borrowed content in `#[derive(PartialOrd, PartialEq)]`~~
- use `intrinsics::unreachable()` instead of `unreachable!()`

I don't believe there are any breaking changes in here, but I do want some more eyes on that.

Fixes #2810 (!), I believe (we still assume that "std" or "core" is the standard library but so does the rest of rustc...).
Fixes #21714 (cc @apasel422).
~~Fixes~~ (postponed) #24047 (cc @withoutboats @bluss).
Fixes #31714 (cc @alexcrichton @bluss).
Fixes #31886 (cc @oli-obk).
durka added a commit to durka/rust that referenced this issue Mar 18, 2016
It was originally intended to be i32, but it isn't.

Fixes rust-lang#31886.
durka added a commit to durka/rust that referenced this issue Mar 18, 2016
bors added a commit that referenced this issue Mar 22, 2016
derive: assume enum repr defaults to isize

derive: assume enum repr defaults to isize

Fixes #31886.

Spawned from #32139.

r? @alexcrichton
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants