-
-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Don't try to evaluate const blocks during constant promotion #150557
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
base: main
Are you sure you want to change the base?
Conversation
|
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
|
Since this adds some extra query calls, a perf run may be warranted. @bors try @rust-timer queue This can be mitigated by reordering the logic to only try const-evaluating after we do other checks (e.g. to make sure we can promote the array we're indexing into, for array indexing), but since this fixes a beta regression, I figure I'll start with a small patch in case there's appetite for a backport. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Don't try to evaluate const blocks during constant promotion
This comment has been minimized.
This comment has been minimized.
c4492d6 to
304c5af
Compare
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (e44c792): comparison URL. Overall result: no relevant changes - no action neededBenchmarking this pull request means it may be perf-sensitive – we'll automatically label it not fit for rolling up. You can override this, but we strongly advise not to, due to possible changes in compiler perf. @bors rollup=never Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)This benchmark run did not return any relevant results for this metric. CyclesResults (primary 2.2%, secondary 0.6%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis benchmark run did not return any relevant results for this metric. Bootstrap: 477.934s -> 476.786s (-0.24%) |
| Const::Ty(..) | Const::Val(..) => true, | ||
| Const::Unevaluated(uc, _) => self.tcx.def_kind(uc.def) != DefKind::InlineConst, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Const::Ty(..) | Const::Val(..) => true, | |
| Const::Unevaluated(uc, _) => self.tcx.def_kind(uc.def) != DefKind::InlineConst, | |
| // `Const::Ty` is always a `ConstKind::Param` right now and that can never be turned | |
| // into a mir value for promotion | |
| // FIXME(mgca): do we want uses of type_const to be normalized during promotion? | |
| Const::Ty(..) => false, | |
| Const::Val(..) => true, | |
| // Other kinds of unevaluated's should be able to cause query cycles too, but | |
| // inline consts *always* cause query cycles | |
| Const::Unevaluated(uc, _) => self.tcx.def_kind(uc.def) != DefKind::InlineConst, |
On stable a Const::Ty is always a ConstKind::Param which you can't do anything meaningful with.
Am I correct in assuming non inline consts can also cause a query cycle here? The following example seems to demonstrate this:
const fn foo() -> &'static u32 {
&(10 / <()>::ASSOC)
}
trait Trait {
const ASSOC: u32;
}
impl Trait for () {
const ASSOC: u32 = *foo();
}I think this is the kind of cycle we're talking about here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cycle there has the same shape, but I find it conceptually a bit different, since it arises from a cyclic dependency in user code: by definition, CTFE of <()>::ASSOC requires CTFE of foo, which requires CTFE of <()>::ASSOC. Even if we weren't trying to evaluate <()>::ASSOC during constant promotion, it and foo are both unusable. Since the cycle is unavoidable, I don't think we need to catch it in constant promotion.
The cycle for inline consts is due to how they're borrow-checked along with their typeck root. If borrow-checking the containing body requires evaluating the constant, this gives rise to a cycle, since evaluating the constant requires borrow-checking it, which again means borrow-checking the containing body. However, using the inline const is perfectly fine as long as we don't evaluate it until after borrow-checking.
I'll tack a note onto to the "other kinds of unevaluated's" comment to clarify why the inline const cycle in particular needs to be avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the suggested change with further elaboration in the comment: (diff)
| // At the time #150464 was reported, the index as evaluated before checking whether the indexed | ||
| // expression and index expression could themselves be promoted. This can't be promoted since | ||
| // it references a local, but it still resulted in a query cycle. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that mean this PR makes that code go from query cycle -> pass? I feel like it'd be nice to be more clear/explicit about which specific code is fixed by this PR. Afaict any code which doesn't actually need promotion to take place but had a const block there will now pass whereas was previously broken by #138499?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's right, yeah. I've made the wording more explicit and added some additional examples to hopefully make the tests flow better conceptually (and catch if changes to constant promotion would make it become out-of-date): (diff)
This feels not-correct. I would expect that a const block would be "transparent" to static promotion - I would expect that all of these would continue to compile. Changing this to not compiling just to fix an implementation-detail (query cycle) seems wrong. |
|
This is something that'd need a language team decision, but I'd argue long term we shouldn't be implicitly promoting integer division or array accesses at all (per #124328). Having a bunch of special cases for things we can promote makes Rust harder to reason about (and harder to write lints/diagnostics for, IME). In this light, my feeling is that we shouldn't complicate the compiler implementation to make |
|
A more targeted alternative to this PR could be to reorder the logic for checking if array indexing can be used in promoted constants, so that it recursively checks the array and index before evaluating the index to see if it's in-bounds. That'd fix the regression reported in #150464 (since it'd bail when finding that the array can't be used in a promoted constant) but would leave in the query cycles for cases where whether we promote something depends on CTFE of an inline constant (like |
304c5af to
d758793
Compare
Also to be clear, there is a clear migration path for affected code: explicit promotion, expressed as |
The |
|
Yeah I was talking about code that needs promotion, i.e. code that worked before #138499 but is still broken after this PR. (Confusingly @theemathas now deleted their comment. Not a good idea to do that after notifications have already been sent. :) |
Clarifying question: would EDIT: from @dianne in the lang meeting, yes, that's still fine because the Also, if this allows more code to compile, it does need a lang FCP because reverting it would break that code again. |
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
|
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
|
The idea, as I'm given to understand, is that query cycles were holding space for what we'd otherwise be breaking here. So we're expecting this to only allow new things to compile. E.g., this will work now: fn main() {
let y = &(1 / const { 1 });
}As @dianne says:
To err on the safe side, let's check this hypothesis that query cycles were holding space for us in all cases with a crater run. @bors try |
This comment has been minimized.
This comment has been minimized.
Don't try to evaluate const blocks during constant promotion
|
@rfcbot reviewed |
|
🔔 This is now entering its final comment period, as per the review above. 🔔 |
|
@rfcbot reviewed |
|
@craterbot check |
|
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
As of #138499, trying to evaluate a const block in anything depended on by borrow-checking will result in a query cycle. Since that could happen in constant promotion, this PR adds a check for const blocks there to stop them from being evaluated.
Admittedly, this is a hack. See #124328 for discussion of a more principled fix: removing cases like this from constant promotion altogether. To simplify the conditions under which promotion can occur, we probably shouldn't be implicitly promoting division or array indexing at all if possible. That would likely require a FCW and migration period, so I figure we may as well patch up the cycle now and simplify later.
Fixes #150464
I'll also lang-nominate this for visibility. I'm not sure there's much to discuss about this PR specifically, but it does represent a change in semantics. In Rust 1.87, the code below compiled. In Rust 1.88, it became a query cycle error. After this PR, it fails to borrow-check because the temporaries can no longer be promoted.