Skip to content

Restrict promotion to infallible operations #58

Closed
@RalfJung

Description

@RalfJung

Proposal

Summary and problem statement

I propose to resolve the "promotion mess" by only promoting code that we know will not fail to const-evaluate.

Motivation, use-cases, and solution sketches

Promotion of code to a const has been a constant source of "challenges" (aka problems) and surprises over the years. The first I saw is this soundness issue and since then we kept accumulating special cases in various parts of the compiler. Also see why we cannot promote arbitrary const fn calls, and this "meta issue".

I think we can solve this problem once and for all by ensuring that we only promote code that cannot fail to const-evaluate. Then we can get rid of all the code in rustc that has to somehow do something when evaluating a promoted failed. If we also make const_err a hard error we can in fact assume that const-evaluation errors are always directly reported to the user, which leads to even further simplifications and enables us to fix some diagnostics issues.

Technical note: it might seem that we have to rule out promotion of arithmetic in debug mode as overflows would cause evaluation failure. That is however not the case. An addition in release mode is compiled to a CheckedAdd MIR operation that never fails, which returns an (<int>, bool), and is followed by a check of said bool to possibly raise a panic. We only ever promote the CheckedAdd, so evaluation of the promoted will never fail, even if the operation overflows.

So I think we should work towards making all CTFE failures hard errors, and I started putting down some notes for that. However, this will require some breaking changes around promotion:

  • We should no longer promote things like &(1/0) or &[2][12]. When promoting fallible operations like division, modulo, and indexing (and I think those are all the fallible operations we promote, but I might have missed something), then we have to make sure that this concrete promoted will not fail -- we need to check for div-by-0 and do the bounds check before accepting an expression for promotion. I propose we check if the index/divisor are constants, in which case the analysis is trivial, and just reject promotion for non-constant indices/divisors. If that is too breaking, a backup plan might be to somehow treat this more like CheckedAdd, where we promote the addition but not the assertion, which does ensure that the promoted never fails to evaluate even on overflow. (But I think that only works for divison/modulo, where we could return a "dummy value"; it doesn't work for indexing in general.)
  • To achieve full "promoteds never fail", we have to severely dial back promotion inside const/static initializers -- basically to the same level as promotion inside fn and const fn. Currently there are two ways in which promotion inside const/static initializers is special here: first of all union field accesses are promoted (I am trying to take that back in stop promoting union field accesses in 'const' rust#77526), and secondly calls to all const fn are promoted. If we cannot take back both of these, we will instead need to treat MIR that originates from const/static initializers more carefully than other MIR -- even in code that ought to compile, there might be constants in there which fail to evaluate, so MIR optimizations and MIR evaluation (aka Miri) need to be careful to not evaluate such consts. This would be some unfortunate technical debt, but in my opinion still way better than the situation we currently find ourselves in.

Alternative: restrict promotion to patterns

I have in the past raised support for restricting promotion even further, namely to only those expressions that would also be legal as patterns. @ecstatic-morse has also expressed support for this goal. However, I now think that this is unnecessarily restrictive -- I do not see any further benefit that we would gain by ruling out expressions that will always succeed, but would not be legal as patterns. In any case, even if we want to go for pattern-only promotion in the future, that would only mean ruling out even more promotion than what I am proposing, so this proposal should still be a reasonable first step in that direction.

Prioritization

I guess this falls under the "Const generics and constant evaluation" priority.

Links and related work

Initial people involved

@ecstatic-morse, @oli-obk and me (aka the const-eval WG) have been talking about this and slowly chipping away at const promotion to make it less ill-behaved. The state described above is the result of as much cleanup as we felt comfortable doing just based on crater runs and the "accidental stabilization" argument.

What happens now?

This issue is part of the experimental MCP process described in RFC 2936. Once this issue is filed, a Zulip topic will be opened for discussion, and the lang-team will review open MCPs in its weekly triage meetings. You should receive feedback within a week or two.

This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions