-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Tracking issue for "Lazy normalization" #60471
Comments
Maybe we could do with an |
@varkor Go for it :) A- labels come cheap and we have too few today imo. |
Did we ever decide whether fixing enforcement of type alias bounds was blocked no lazy norm? |
I've added a label for lazy normalisation and added it to a number of const generic issues I think are blocked on it. |
Just had a crazy idea that could help us start testing a bunch of stuff right away: This will break things (object safety comes to mind, IIRC there are a bunch of others), but in the context of tiny self-contained tests, it might be enough to allow testing expressions like |
I tried this out here: https://github.com/varkor/rust/tree/defer_normalization #![crate_type = "lib"]
#![feature(defer_normalization)]
pub unsafe fn size_of_units<T: Sized>() -> [(); std::mem::size_of::<T>()] {
[(); std::mem::size_of::<T>()]
} results in:
|
All of those are legitimate bugs that we'd need to fix sooner or later. But then again, what you wrote would, I believe, fail the Also, what if you put That is, I expect type-checking bodies where such a type-level const expression is still parametrized by generics, to require more work. |
I'm still not sure exactly what lazy normalization is. Why do we need it? What parts of the compiler will need to be changed in order to implement it? |
@Aaron1011 Normalization is replacing "projections" (such as Right now all of this is done "eagerly", i.e. as soon as possible, and always (during typeck, or whenever there is enough information to normalize further), but that causes some issues:
Lazy normalization would simply defer the work of resolving/evaluating such type-level constructs until the very moment they are needed (such as when requiring that two types are the same, or when computing the low-level layout of a type for miri/codegen). |
Great explanation, @eddyb. I've seen this question come up often, so I'm going to have to bookmark that comment and link people to it! |
I'm interested in working in this. What would the the best place to start? Would it make sense to work off of @varkor's branch, add a |
@Aaron1011 It's not really a caller-by-caller basis. The same callers that cause the cycles also need early normalization right now. The solution (ignoring the hack I mentioned and which @varkor was playing with) is to add lazy normalization, which is something @nikomatsakis didn't let me, for years, so I assume there is a good reason (likely regarding associated types). Some infrastructure has been slowly making its way into the compiler, for Chalk integration (but not only), so things might be reaching a point where we can have lazy normalization. I wouldn't bother without a more thorough discussion with @nikomatsakis and @rust-lang/wg-traits. |
Just because this is important, let me spell it out: Getting this wrong can introduce unsoundness and I doubt we have enough tests to prevent that That is, there may be tricks one might use to get all the code samples we have working, but introduce a subtle unsoundness regarding associated types, perhaps with HRTB involved, or in constants. It's very easy to lose track of obligations (yet-to-be-proven where clauses, roughly) that are required to hold, and we plugged holes for years after 1.0. |
…li-obk rustc_typeck: gate AnonConst's generics on feature(const_generics). This PR employs the fix for rust-lang#43408 when `#![feature(const_generics)]` is enabled, making the feature-gate the opt-in for all the possible breakage this may incur. For example, if this PR lands, this will cause a cycle error (due to rust-lang#60471): ```rust #![feature(const_generics)] fn foo<T: Into<[u8; 4]>>() {} ``` And so will anything with type-level const expressions, in its bounds. Surprisingly, `impl`s don't seem to be affected (if they were, even libcore wouldn't compile). One thing I'm worried about is not knowing how much unstable code out there, using const-generics, will be broken. But types like `Foo<{N+1}>` never really worked, and do after this PR, just not in bounds - so ironically, it's type-level const expressions that don't depend on generics, which will break (in bounds). Also, if we do this, we'll have effectively blocked stabilization of const generics on rust-lang#60471. r? @oli-obk cc @varkor @yodaldevoid @nikomatsakis
Am I correct in assuming that the fix for this will fix #67753 ? |
@Manishearth: yes, it ought to. |
Lazy normalization of constants (Reprise) Continuation of rust-lang#67890 by @Skinny121. Initial implementation of rust-lang#60471 for constants. Perform normalization/evaluation of constants lazily, which is known as lazy normalization. Lazy normalization is only enabled when using `#![feature(lazy_normalization_consts)]`, by default constants are still evaluated eagerly as there are currently. Lazy normalization of constants is achieved with a new ConstEquate predicate which type inferences uses to delay checking whether constants are equal to each other until later, avoiding cycle errors. Note this doesn't allow the use of generics within repeat count expressions as that is still evaluated during conversion to mir. There are also quite a few other known problems with lazy normalization which will be fixed in future PRs. r? @nikomatsakis fixes rust-lang#71922, fixes rust-lang#71986
Lazy normalization of constants (Reprise) Continuation of rust-lang#67890 by @Skinny121. Initial implementation of rust-lang#60471 for constants. Perform normalization/evaluation of constants lazily, which is known as lazy normalization. Lazy normalization is only enabled when using `#![feature(lazy_normalization_consts)]`, by default constants are still evaluated eagerly as there are currently. Lazy normalization of constants is achieved with a new ConstEquate predicate which type inferences uses to delay checking whether constants are equal to each other until later, avoiding cycle errors. Note this doesn't allow the use of generics within repeat count expressions as that is still evaluated during conversion to mir. There are also quite a few other known problems with lazy normalization which will be fixed in future PRs. r? @nikomatsakis fixes rust-lang#71922, fixes rust-lang#71986
I just ran into this issue. The last comment was half a year ago, could anyone tell me what the current status of this is? |
Any news? |
Is lazy normalization dead? Waiting for the next trait solver? Eager normalization is causing a whole slew of issues on my current project which attempts to provide a stable ABI for Rust with niche exploitation as a library. To do so, I rely on |
I believe this issue is no longer relevant as per #72219 (comment) (@lcnr you may want to close this). I would check https://github.com/rust-lang/project-const-generics/issues?q=is%3Aissue+is%3Aopen+label%3AA-generic-exprs, #92827, and #76560. I don't believe there is one central place to find updates, but these issues might be what you are looking for based on my limited understanding. It sounds like Chalk is part of the solution for this, but overall you can see that there is work happening on this and discussions over time using those links. I am also eagerly awaiting these kinds of features for implementing a generic machine learning library with compile-time tensor sizes and monomorphic swappable backends (via GATs), so I also follow a lot of these compiler features hoping they will be fully implemented one day. The team also seems to communicate on Zulip as well, but I have not visited recently. |
I heard somewhere that Chalk was getting supplanted by yet another trait solver, but I may be wrong there... Anyway, really hyped for when we'll get |
Finally taking the time to go actually sit down and look at this issue again 😅 Going to try and explain two things with this comment: what's actually meant when talking about lazy normalization and the current status. I think at its core we have a lot of different things which we talk about under the name of "lazy normalization" and I am still figuring out where to better document all of this. As stated above by @eddyb, normalization is the process of taking a projection/expression/alias and converting it to its "underlying value". Dealing with aliases in the type system is imo the most involved part of Rusts type system. One of the main challenges is that aliases do not need to be structurally equal to be semantically equal. For structs and enums, if you have Normalizing an aliases to its underlying type can either
With this context, what are the issues we want to solve by "normalizing lazily" normalizing constant expressions causes query cyclesImagine a constant in our fn foo<T>()
where
T: IntoIterator<Item = u32>,
[u8; std::mem::size_of::<<T as IntoIterator>::Item>()]: SomeTrait,
{
...
} To evaluate that constant it has to know that when normalization is ambiguous, we have to defer equality checksIf we were to equate We're slowly moving towards calling this "deferred projection/alias equality" and it is implemented in the new trait solver but not yet in the currently stable one. Due to reasons™, this only causes issues for projections which mention higher ranked regions. requiring normalized environments is fundamentally brokenWithout deferred projection equality, we need all aliases in the environment - the we have to eagerly normalize everywhereThere are a lot of places from which we can get unnormalized aliases, so in the current trait solver and also in HIR typeck (i.e. during type inference) we have an incredible amount of normalization calls and still get weird errors when we miss some normalization call somewhere. We don't eagerly normalize inside of the new trait solver itself, but at least when initially stabilizing, we will keep eager normalization in HIR typeck. Having projections around "longer than necessary" results in a bunch of issues and we actually want to stabilize the new solver at some point :p |
You know... The rustc-dev-guide would be a great place for this explanation to live ;)
|
What is this?
"Lazy normalization" is a change to how we handle associated types (and constants) so that we wait until we have to equate an associated type (or constant) has to be equated or processed to normalize it (i.e., figure out if there is an impl we can use to find its definition), rather than doing so eagerly. This has a number of advantages, and in particular for const generics it can prevent a large number of cyclic errors.
Subissues
Further background reading
What is this "lazy normalization"? (see #60471 (comment))
The text was updated successfully, but these errors were encountered: