Skip to content

Conversation

@dianne
Copy link
Contributor

@dianne dianne commented Dec 31, 2025

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.

let (x, y, z);
// We only promote array indexing if the index is known to be in-bounds.
x = &([0][const { 0 }] & 0);
// We only promote integer division if the divisor is known not to be zero.
y = &(1 / const { 1 });
// Furthermore, if the divisor is `-1`, we only promote if the dividend is
// known not to be `int::MIN`.
z = &(const { 1 } / -1);
// The borrowed temporaries can't be promoted, so they were dropped at the ends
// of their respective statements.
(x, y, z);

@rustbot
Copy link
Collaborator

rustbot commented Dec 31, 2025

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 31, 2025
@rustbot
Copy link
Collaborator

rustbot commented Dec 31, 2025

r? @lcnr

rustbot has assigned @lcnr.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@dianne dianne added T-lang Relevant to the language team I-lang-nominated Nominated for discussion during a lang team meeting. labels Dec 31, 2025
@dianne
Copy link
Contributor Author

dianne commented Dec 31, 2025

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.

@rust-timer

This comment has been minimized.

@rust-bors

This comment has been minimized.

rust-bors bot added a commit that referenced this pull request Dec 31, 2025
Don't try to evaluate const blocks during constant promotion
@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Dec 31, 2025
@rust-log-analyzer

This comment has been minimized.

@dianne dianne force-pushed the no-const-block-eval-in-promotion branch from c4492d6 to 304c5af Compare December 31, 2025 22:54
@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 1, 2026

☀️ Try build successful (CI)
Build commit: e44c792 (e44c792db0a3a17223067eaa688596a57d30423f, parent: 8d670b93d40737e1b320fd892c6f169ffa35e49e)

@rust-timer

This comment has been minimized.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (e44c792): comparison URL.

Overall result: no relevant changes - no action needed

Benchmarking 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
@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

This 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.

Cycles

Results (primary 2.2%, secondary 0.6%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.2% [2.2%, 2.2%] 1
Regressions ❌
(secondary)
2.8% [2.8%, 2.8%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-1.5% [-1.5%, -1.5%] 1
All ❌✅ (primary) 2.2% [2.2%, 2.2%] 1

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 477.934s -> 476.786s (-0.24%)
Artifact size: 390.83 MiB -> 390.85 MiB (0.01%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jan 1, 2026
@traviscross traviscross added I-lang-radar Items that are on lang's radar and will need eventual work or consideration. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang labels Jan 1, 2026
@cuviper
Copy link
Member

cuviper commented Jan 2, 2026

Since #150464 is a beta regression from crater:

@rustbot label beta-nominated

However, there was only one failure of this sort in the whole crater run, and further minimizations also failed back to 1.88, so it's probably not a big deal if we let it ride.

@rustbot rustbot added the beta-nominated Nominated for backporting to the compiler in the beta channel. label Jan 2, 2026
Comment on lines 694 to 695
Const::Ty(..) | Const::Val(..) => true,
Const::Unevaluated(uc, _) => self.tcx.def_kind(uc.def) != DefKind::InlineConst,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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?

Copy link
Contributor Author

@dianne dianne Jan 2, 2026

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.

Copy link
Contributor Author

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)

Comment on lines 21 to 23
// 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.
Copy link
Member

@BoxyUwU BoxyUwU Jan 2, 2026

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?

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 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)

@jackh726
Copy link
Member

jackh726 commented Jan 3, 2026

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.

let (x, y, z);
// We only promote array indexing if the index is known to be in-bounds.
x = &([0][const { 0 }] & 0);
// We only promote integer division if the divisor is known not to be zero.
y = &(1 / const { 1 });
// Furthermore, if the divisor is `-1`, we only promote if the dividend is
// known not to be `int::MIN`.
z = &(const { 1 } / -1);
// The borrowed temporaries can't be promoted, so they were dropped at the ends
// of their respective statements.
(x, y, z);

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.

@dianne
Copy link
Contributor Author

dianne commented Jan 3, 2026

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 &(1 / const { 1 }) promote.

@dianne
Copy link
Contributor Author

dianne commented Jan 3, 2026

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 &([0][const { 0 }] & 0))

@dianne dianne force-pushed the no-const-block-eval-in-promotion branch from 304c5af to d758793 Compare January 3, 2026 09:51
@RalfJung
Copy link
Member

RalfJung commented Jan 7, 2026

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 &(1 / const { 1 }) promote.

Also to be clear, there is a clear migration path for affected code: explicit promotion, expressed as const { &(1 / 1) }.

@dianne
Copy link
Contributor Author

dianne commented Jan 7, 2026

the code that ran into this in the crater run had the form: assert_eq!(a, b[offset_of!(Type, field)] & c);, which doesn't have an obvious place to put the const block. Although you could still assign it in a variable instead.

The b[...] & c there doesn't need to be promoted to compile (and for many ways of writing b and c, it can't be promoted anyway). If it did need to be promoted though, you could put the entire b[...] & c expression in a const block. Possibly a more contrived macro could result in something that couldn't straightforwardly be migrated, but I'd be surprised if implicit promotion was relied on in such a case.

@traviscross traviscross removed I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-0 Lang team prioritization drag level 0.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. labels Jan 21, 2026
@rust-rfcbot rust-rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Jan 24, 2026
@rust-rfcbot
Copy link
Collaborator

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

@rust-rfcbot rust-rfcbot added the to-announce Announce this issue on triage meeting label Jan 24, 2026
@traviscross traviscross added the relnotes Marks issues that should be documented in the release notes of the next release. label Jan 24, 2026
Copy link
Contributor

@lcnr lcnr left a comment

Choose a reason for hiding this comment

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

one nit, otherwise r=me

View changes since this review

if let TempState::Defined { location: loc, .. } = self.temps[local]
&& let Left(statement) = self.body.stmt_at(loc)
&& let Some((_, Rvalue::Use(Operand::Constant(c)))) = statement.kind.as_assign()
&& self.is_evaluable(c.const_)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
&& self.is_evaluable(c.const_)
&& self.should_evaluate_for_promotion_checks(c.const_)

maybe. I feel like is_evaluatable is somewhat confusing of a name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should_evaluate_for_promotion_checks does feel clearer, yeah; I've made that change. It is a bit unfortunate for ease of scanning to go from

Operand::Constant(c) if self.is_evaluable(c.const_) => {

to

Operand::Constant(c)
    if self.should_evaluate_for_promotion_checks(
        c.const_,
    ) =>
{

but the nested/sequential matches that's a part of could maybe benefit from some restructuring anyway, which could remove some of the indentation. I'll save that potential cleanup for another PR

@dianne dianne force-pushed the no-const-block-eval-in-promotion branch from d758793 to ae9b9cb Compare January 27, 2026 02:48
@rustbot
Copy link
Collaborator

rustbot commented Jan 27, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

Copy link
Contributor

@lcnr lcnr left a comment

Choose a reason for hiding this comment

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


struct Thing(i32);

fn main() {
Copy link
Contributor

Choose a reason for hiding this comment

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

one more thought, this test currently has some statements which should compile and others which should error, can you split them into 2 revisions or tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

opted for 2 revisions to keep the context for the tests all in one place

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 27, 2026

✌️ @dianne, you can now approve this pull request!

If @lcnr told you to "r=me" after making some further change, then please make that change and post @bors r=lcnr.

@dianne dianne force-pushed the no-const-block-eval-in-promotion branch from ae9b9cb to 20f46a4 Compare January 27, 2026 11:08
@rust-log-analyzer

This comment has been minimized.

@dianne dianne force-pushed the no-const-block-eval-in-promotion branch from 20f46a4 to 4039cef Compare January 27, 2026 12:32
@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-tools failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
REPOSITORY                                   TAG       IMAGE ID       CREATED      SIZE
ghcr.io/dependabot/dependabot-updater-core   latest    354d02aa29ac   7 days ago   783MB
=> Removing docker images...
Deleted Images:
untagged: ghcr.io/dependabot/dependabot-updater-core:latest
untagged: ghcr.io/dependabot/dependabot-updater-core@sha256:596da3f22bcbdff2c96fd7126001278022c834c1621c5efa2ad1a7794590636c
deleted: sha256:354d02aa29acf525570c732b6e006ecf138de6d63ca525d552eb4b24880ddc6c
deleted: sha256:8b7af0e426bc2cbeeacfd96b8354d3b80016991520977197e62090e47abaede8
deleted: sha256:cadf11ef1de7fdd5eab563757942353684047f09b212dc99d6ed48e8acf34d62
deleted: sha256:569b0caf9d5285db44ccd2629a3470139eea755be423a33a54d8a24cb3926bfa
deleted: sha256:f9dc5feb048d8f9fd43137e3998f59e9acfbd76c47a4e14984d109654119e282
---
tests/ui/drain_collect.fixed ... ok
tests/ui/double_parens.rs ... ok
tests/ui/duplicated_attributes.rs ... ok
tests/ui/duplicate_underscore_argument.rs ... ok
tests/ui/duration_suboptimal_units.rs ... ok
tests/ui/duration_suboptimal_units_days_weeks.rs ... ok
tests/ui/duration_subsec.rs ... ok
tests/ui/duration_suboptimal_units.fixed ... ok
tests/ui/double_parens.fixed ... ok
tests/ui/duration_suboptimal_units_days_weeks.fixed ... ok
tests/ui/duration_subsec.fixed ... ok
tests/ui/else_if_without_else.rs ... ok
tests/ui/empty_docs.rs ... ok
tests/ui/elidable_lifetime_names.rs ... ok
tests/ui/eager_transmute.rs ... ok
---
[WARNING] line 39: Delta is 0 for "x", maybe try to use `compare-elements-position` instead?

======== tests/rustdoc-gui/setting-go-to-only-result.goml ========

[ERROR] setting-go-to-only-result output:
Execution context was destroyed, most likely because of a navigation.
stack: Error: Execution context was destroyed, most likely because of a navigation.
    at rewriteError (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:457:15)
    at async #evaluate (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:389:60)
    at async ExecutionContext.evaluate (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:277:16)
    at async IsolatedWorld.evaluate (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/IsolatedWorld.js:100:16)
    at async CdpFrame.evaluate (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/puppeteer-core/lib/cjs/puppeteer/api/Frame.js:362:20)
    at async CdpPage.evaluate (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/puppeteer-core/lib/cjs/puppeteer/api/Page.js:826:20)
    at async /checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/browser-ui-test/src/index.js:432:28
    at async waitForConditionTrue (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/browser-ui-test/src/utils.js:209:13)
    at async runAllCommands (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/browser-ui-test/src/index.js:431:22)
    at async innerRunTestCode (/checkout/obj/build/x86_64-unknown-linux-gnu/test/rustdoc-gui/node_modules/browser-ui-test/src/index.js:714:21)



<= doc-ui tests done: 146 succeeded, 1 failed, 0 filtered out

@dianne
Copy link
Contributor Author

dianne commented Jan 27, 2026

I'm seeing that failure on other PRs, so it seems like there's a pretty high chance of hitting it at the moment; I'll hold off for now on trying for green CI. In the mean time though, since perf was clean and merge conflicts seem relatively unlikely, @bors rollup

@lcnr
Copy link
Contributor

lcnr commented Jan 27, 2026

@bors r+

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 27, 2026

📌 Commit 4039cef has been approved by lcnr

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 27, 2026
rust-bors bot pushed a commit that referenced this pull request Jan 27, 2026
…uwer

Rollup of 4 pull requests

Successful merges:

 - #151161 (std: move time implementations to `sys`)
 - #151694 (more `proc_macro` bridge cleanups)
 - #151711 (stdarch subtree update)
 - #150557 (Don't try to evaluate const blocks during constant promotion)
@rust-bors rust-bors bot merged commit 53fb684 into rust-lang:main Jan 27, 2026
6 of 11 checks passed
rust-timer added a commit that referenced this pull request Jan 27, 2026
Rollup merge of #150557 - dianne:no-const-block-eval-in-promotion, r=lcnr

Don't try to evaluate const blocks during constant promotion

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.

```rust
let (x, y, z);
// We only promote array indexing if the index is known to be in-bounds.
x = &([0][const { 0 }] & 0);
// We only promote integer division if the divisor is known not to be zero.
y = &(1 / const { 1 });
// Furthermore, if the divisor is `-1`, we only promote if the dividend is
// known not to be `int::MIN`.
z = &(const { 1 } / -1);
// The borrowed temporaries can't be promoted, so they were dropped at the ends
// of their respective statements.
(x, y, z);
```
@rustbot rustbot added this to the 1.95.0 milestone Jan 27, 2026
@dianne dianne deleted the no-const-block-eval-in-promotion branch January 28, 2026 00:16
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Jan 28, 2026
…uwer

Rollup of 4 pull requests

Successful merges:

 - rust-lang/rust#151161 (std: move time implementations to `sys`)
 - rust-lang/rust#151694 (more `proc_macro` bridge cleanups)
 - rust-lang/rust#151711 (stdarch subtree update)
 - rust-lang/rust#150557 (Don't try to evaluate const blocks during constant promotion)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

beta-nominated Nominated for backporting to the compiler in the beta channel. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team to-announce Announce this issue on triage meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

regression: "cycle detected when borrow-checking" in offset_of!