-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Implement RFC-2011 (Nicer assert!
messages)
#96496
Conversation
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
r? @cjgillot (rust-highfive has picked a reviewer for you, use r? to override) |
I guess the code is now ready for review |
We should check the performance impact of this once it's ready (I guess there's more to do?). I know that I've seen large differences in tight loops between I don't think this is a blocker (the functionality is very nice) but once yeah, it probably warrants a perf run. |
Shouldn't the format machinery not affect the performance of the assert unless it fails? Or is there some weird stuff going on in the codegen? |
Unfortunately, I can not answer all the questions because this PR is still a work in progress. Performance heavily depends on codegen but I will try to do my best to make everything work as fast as possible. |
Reviewing should be easier now that the code is in a much better shape |
This helps, but there is significantly more codegen not shown in the example -- this impacts performance due to icache usage even if the branch is not taken. Anyway, as I said, I don't think this blocks things, but I do think we should be aware of the performance impact of this (which may be small... and admittedly, possibly not measurable by our tools, which go by instruction counts, and thus will not judge things like icache impact), so I think we should do a timer run. @bors try @rust-timer queue |
Awaiting bors try build completion. @rustbot label: +S-waiting-on-perf |
⌛ Trying commit eae545d20fd3e6aa6579f87ee989c5125340beaa with merge f91eeed48dde360eaa79c339591316da5fa20226... |
@thomcc The new code is behind a feature called |
Shoot. |
This comment has been minimized.
This comment has been minimized.
066a3e7
to
d8b749a
Compare
How does this interact with panicking in const contexts? |
With the feature disabled, nothing changes. With the feature enabled, I am not sure :) |
FWIW, implementation of this and many other built-in macros can be simplified to a usual proc macro level by implementing a version of |
[RFC 2011] Library code CC rust-lang#96496 Based on https://github.com/dtolnay/case-studies/tree/master/autoref-specialization. Basically creates two traits with the same method name. One trait is generic over any `T` and the other is specialized to any `T: Printable`. The compiler will then call the corresponding trait method through auto reference. ```rust fn main() { let mut a = Capture::new(); let mut b = Capture::new(); (&Wrapper(&1i32)).try_capture(&mut a); // `try_capture` from `TryCapturePrintable` (&Wrapper(&vec![1i32])).try_capture(&mut b); // `try_capture` from `TryCaptureGeneric` assert_eq!(format!("{:?}", a), "1"); assert_eq!(format!("{:?}", b), "N/A"); } ``` r? `@scottmcm`
r? @oli-obk |
[RFC 2011] Basic compiler infrastructure Splitting rust-lang#96496 into smaller pieces as was done in rust-lang#97233. Hope review will be easier. This PR practically contains no logic and only serves as a building ground for the actual code that will be placed in a posterior step. * Adds `context.rs` to place the new `assert!` logic. Has a lot of unused elements but all of them are used by the implementation. * Creates an unstable flag because the feature is not yet complete and also to allow external feedback. * Creates the necessary `sym` identifiers that are mostly based on the library elements -> https://github.com/rust-lang/rust/blob/master/library/core/src/asserting.rs * Modifies `assert.rs` to branch to `context.rs` if the unstable flag is enabled. * Adds a test to satisfy tidy but the test does nothing in reality.
☔ The latest upstream changes (presumably #97654) made this pull request unmergeable. Please resolve the merge conflicts. |
[RFC 2011] Minimal initial implementation Tracking issue: rust-lang#44838 Third step of rust-lang#96496 Implementation has ~290 LOC with the bare minimum to be in a functional state. Currently only searches for binary operations to mimic what `assert_eq!` and `assert_ne!` already do. r? `@oli-obk`
[RFC 2011] Minimal initial implementation Tracking issue: rust-lang#44838 Third step of rust-lang#96496 Implementation has ~290 LOC with the bare minimum to be in a functional state. Currently only searches for binary operations to mimic what `assert_eq!` and `assert_ne!` already do. r? ``@oli-obk``
[RFC 2011] Minimal initial implementation Tracking issue: rust-lang#44838 Third step of rust-lang#96496 Implementation has ~290 LOC with the bare minimum to be in a functional state. Currently only searches for binary operations to mimic what `assert_eq!` and `assert_ne!` already do. r? `@oli-obk`
[RFC 2011] Minimal initial implementation Tracking issue: rust-lang#44838 Third step of rust-lang#96496 Implementation has ~290 LOC with the bare minimum to be in a functional state. Currently only searches for binary operations to mimic what `assert_eq!` and `assert_ne!` already do. r? `@oli-obk`
Closing now that #97665 was merged. @petrochenkov @scottmcm @oli-obk Thank you all for making this RFC possible. |
[RFC 2011] Expand expressions where possible Tracking issue: rust-lang#44838 Fourth step of rust-lang#96496 Extends rust-lang#97665 considering expressions that are good candidates for expansion. r? `@oli-obk`
[RFC 2011] Optimize non-consuming operators Tracking issue: rust-lang#44838 Fifth step of rust-lang#96496 The most non-invasive approach that will probably have very little to no performance impact. ## Current behaviour Captures are handled "on-the-fly", i.e., they are performed in the same place expressions are located. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( { ***try capture `a` and then return `a`*** } > 1 && { ***try capture `b` and then return `b`*** } < 100 ) { panic!( ... ); } ``` As such, some overhead is likely to occur (Specially with very large chains of conditions). ## New behaviour for non-consuming operators When an operator is known to not take `self`, then it is possible to capture variables **AFTER** the condition. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( a > 1 && b < 100 ) { { ***try capture `a`*** } { ***try capture `b`*** } panic!( ... ); } ``` So the possible impact on the runtime execution time will be diminished. r? `@oli-obk`
[RFC 2011] Optimize non-consuming operators Tracking issue: rust-lang#44838 Fifth step of rust-lang#96496 The most non-invasive approach that will probably have very little to no performance impact. ## Current behaviour Captures are handled "on-the-fly", i.e., they are performed in the same place expressions are located. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( { ***try capture `a` and then return `a`*** } > 1 && { ***try capture `b` and then return `b`*** } < 100 ) { panic!( ... ); } ``` As such, some overhead is likely to occur (Specially with very large chains of conditions). ## New behaviour for non-consuming operators When an operator is known to not take `self`, then it is possible to capture variables **AFTER** the condition. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( a > 1 && b < 100 ) { { ***try capture `a`*** } { ***try capture `b`*** } panic!( ... ); } ``` So the possible impact on the runtime execution time will be diminished. r? ``@oli-obk``
[RFC 2011] Optimize non-consuming operators Tracking issue: rust-lang#44838 Fifth step of rust-lang#96496 The most non-invasive approach that will probably have very little to no performance impact. ## Current behaviour Captures are handled "on-the-fly", i.e., they are performed in the same place expressions are located. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( { ***try capture `a` and then return `a`*** } > 1 && { ***try capture `b` and then return `b`*** } < 100 ) { panic!( ... ); } ``` As such, some overhead is likely to occur (Specially with very large chains of conditions). ## New behaviour for non-consuming operators When an operator is known to not take `self`, then it is possible to capture variables **AFTER** the condition. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( a > 1 && b < 100 ) { { ***try capture `a`*** } { ***try capture `b`*** } panic!( ... ); } ``` So the possible impact on the runtime execution time will be diminished. r? ```@oli-obk```
[RFC 2011] Optimize non-consuming operators Tracking issue: rust-lang#44838 Fifth step of rust-lang#96496 The most non-invasive approach that will probably have very little to no performance impact. ## Current behaviour Captures are handled "on-the-fly", i.e., they are performed in the same place expressions are located. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( { ***try capture `a` and then return `a`*** } > 1 && { ***try capture `b` and then return `b`*** } < 100 ) { panic!( ... ); } ``` As such, some overhead is likely to occur (Specially with very large chains of conditions). ## New behaviour for non-consuming operators When an operator is known to not take `self`, then it is possible to capture variables **AFTER** the condition. ```rust // `let a = 1; let b = 2; assert!(a > 1 && b < 100);` if !( a > 1 && b < 100 ) { { ***try capture `a`*** } { ***try capture `b`*** } panic!( ... ); } ``` So the possible impact on the runtime execution time will be diminished. r? ````@oli-obk````
[RFC 2011] Library code CC rust-lang/rust#96496 Based on https://github.com/dtolnay/case-studies/tree/master/autoref-specialization. Basically creates two traits with the same method name. One trait is generic over any `T` and the other is specialized to any `T: Printable`. The compiler will then call the corresponding trait method through auto reference. ```rust fn main() { let mut a = Capture::new(); let mut b = Capture::new(); (&Wrapper(&1i32)).try_capture(&mut a); // `try_capture` from `TryCapturePrintable` (&Wrapper(&vec![1i32])).try_capture(&mut b); // `try_capture` from `TryCaptureGeneric` assert_eq!(format!("{:?}", a), "1"); assert_eq!(format!("{:?}", b), "N/A"); } ``` r? `@scottmcm`
Based on #48973
CC #44838
Functionality
The intention here is to show the contents of variables that are outside the declaration of
assert!( ... )
.Current behavior
New behavior with this PR
Internals
All external
assert!
variables are replaced by a block that tries to copy the intended value and then use this possibly copied element to print useful messages if a branch occurs.With this reasoning in mind, the above
assert!
will more or less expand to the following code:The compiler "side" outputs the necessary machinery to make the library "side" decide how an element
T
will or will not be printed.Performance
It is inevitable that more instructions will be issued but probably nothing that will have a huge negative impact. Nevertheless, it is possible to optimize certain scenarios to reduce codegen.