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

Avoid bevy_reflect::List::iter wrapping in release mode #13271

Merged
merged 4 commits into from May 12, 2024

Conversation

rmsthebest
Copy link
Contributor

Objective

Fixes #13230

Solution

Uses solution described in #13230
They mention a worry about adding a branch, but I'm not sure there is one.

This code

#[no_mangle]
pub fn next_if_some(num: i32, b: Option<bool>) -> i32 {
    num + b.is_some() as i32
}

produces this assembly with opt-level 3

next_if_some:
        xor     eax, eax
        cmp     sil, 2
        setne   al
        add     eax, edi
        ret

Testing

Added test from #13230, tagged it as ignore as it is only useful in release mode and very slow if you accidentally invoke it in debug mode.


Changelog

Iterationg of ListIter will no longer overflow and wrap around

Migration Guide

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-Reflection Runtime information about types labels May 7, 2024
@alice-i-cecile alice-i-cecile requested a review from MrGVSV May 7, 2024 10:30
@alice-i-cecile alice-i-cecile added D-Straightforward Simple bug fixes and API improvements, docs, test and examples X-Uncontroversial This work is generally agreed upon labels May 7, 2024
@rmsthebest rmsthebest force-pushed the listwrap branch 2 times, most recently from b2e7d86 to da7ec0d Compare May 7, 2024 10:52
@Brezak
Copy link
Contributor

Brezak commented May 7, 2024

I think you're underestimating how long the test will take.
I've written some code that should just test the increment.

use std::{hint::black_box, time::Instant};

fn next_if_some(num: u32, b: Option<bool>) -> u32 {
    num + b.is_some() as u32
}

pub fn main() {
    let mut val = 0;
    let start = Instant::now();
    for _ in 0..(u32::MAX) {
        // Both black boxes are needed to so this doesn't get optimized to a no op
        val = black_box(next_if_some(val, black_box(None)))
    }

    let elapsed = start.elapsed();
    println!("Took {:?}", start.elapsed());

    let mut projected = elapsed;
    // Attempt to calculate how long the loop would take if we tested til usize::MAX (on 64 bit cpu)
    for _ in 0..32 {
        projected *= 2;
    }

    println!("Projected for u64 {:?}", projected)
}

Running it on my Ryzen 7 7800x3d outputs:

Took 1.7983411s
Projected for u64 7723814923.0624768s

7723814923.0624768 seconds is 244 years!
The loop in the test might get optimized out, but I don't know if we can rely on this.

@Brezak
Copy link
Contributor

Brezak commented May 7, 2024

This code won't handle vecs with zsts. One can construct a vector with usize::MAX elements today, like this:

fn max_zst_vec() -> Vec<()> {
    let mut data: Vec<()> = Vec::new();
    
    // SAFETY: A zst vec has a capacity of usize::MAX
    unsafe { data.set_len(usize::MAX) }
    data
}

Your code will:

  • Iterate to the last element.
  • Get the last element.
  • Increment the index.
  • The index being usize::MAX overflows into 0.
  • Iterator loops forever!

@rmsthebest
Copy link
Contributor Author

rmsthebest commented May 7, 2024

Maybe I'm stupid but if you have usize::MAX capacity the last element index is usize::MAX-1?

I changed the test to use a zero size type vec and removed the loop though, let me know if you think it's okay.

Edit: maybe I should stop force pushing, I see it looks a bit strange on the code review parts. Maybe someone can let me know the preferred way of working here

@Brezak
Copy link
Contributor

Brezak commented May 7, 2024

Maybe I'm stupid but if you have usize::MAX capacity the last element index is usize::MAX-1?

You're not stupid. I was wrong.

Copy link
Contributor

@soqb soqb left a comment

Choose a reason for hiding this comment

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

simple, correct change. LGTM!

crates/bevy_reflect/src/list.rs Outdated Show resolved Hide resolved
@alice-i-cecile alice-i-cecile added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label May 7, 2024
Copy link
Member

@MrGVSV MrGVSV left a comment

Choose a reason for hiding this comment

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

Looks good! Thanks for writing the test as well!

It would be great if we could do this for the other iterator types as well, but that isn't a blocker for this PR.

crates/bevy_reflect/src/list.rs Show resolved Hide resolved
@@ -522,4 +524,23 @@ mod tests {
assert_eq!(index, value);
}
}

#[cfg(not(debug_assertions))]
Copy link
Member

Choose a reason for hiding this comment

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

Does CI run tests in release mode? If not, it might be something we raise an issue for such that tests like these can be run.

Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -374,7 +374,7 @@ impl<'a> Iterator for ArrayIter<'a> {
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let value = self.array.get(self.index);
self.index += 1;
self.index += value.is_some() as usize;
Copy link
Member

Choose a reason for hiding this comment

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

Awesome! Thanks for adding these as well!

Would also be able to duplicate that test for each of these changes as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldnt test the overflow for most of them because I couldnt create an iterator with enough elements.
But the logic of not incrementing past the first None I can test, so I've added tests for that

@BD103 BD103 added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 8, 2024
@MrGVSV MrGVSV added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels May 10, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue May 12, 2024
Merged via the queue into bevyengine:main with commit 2783803 May 12, 2024
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Reflection Runtime information about types C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bevy_reflect::List::iter wraps silently on release
6 participants