Skip to content

Promotion rules #2

Closed
Closed
@oli-obk

Description

@oli-obk

Promotion is the process of allowing let x: &'static u32 = &1; to be valid in Rust. Technically the &1 produces a temporary local variable and references to that don't have the 'static lifetime. But there is a check which figures out expressions that are constant and then another pass inserts an unnameable static for the value and refers to that static instead.

This was introduced in rust-lang/rfcs#1414 and I'm doing a write up for it in https://github.com/rust-rfcs/const-eval/blob/master/promotion.md

Now there are some headaches with promotion. E.g.

let x: &'static usize = &(0 - 1);

which, while being const evaluable, would cause a const eval error due to overflow.
This is fine in the above case, since the user obviously requested promotion by setting the 'static lifetime, but

let x: &usize = &(0 - 1);

did not and would still get promoted and then error. We work around this by leaving in the debug assertions that the compiler has on integer arithmetic, which mean even though we promoted the computation to a static, all the checks are still done at runtime (unless optimized out due to guaranteed uselessness).

So everything is fine in these simple cases, it gets problematic when there's code that can't be evaluated at compile-time but which would also not panic at runtime. E.g.

union Foo { x: &'static i32, y: usize }
let x: &bool = unsafe { Foo { x: &1 }.y == Foo { x: &2 }.y };

This errors during const eval because const eval does not compare pointers. We could do pointer comparisons, and in this case it would even be fine because &1 and &2 can never be the same address, but

union Foo { x: &'static i32, y: usize }
let x: &bool = unsafe { Foo { x: &1 }.y == Foo { x: &1 }.y };

is not so clear-cut. LLVM might decide to put both &1 and the other &1 into the same static. Then the addresses would be the same. Or it would not do that, then the addresses are different. When we move to other operators, the result isn't even decideable by LLVM anymore (only the linker knows this):

union Foo { x: &'static i32, y: usize }
let x: &bool = unsafe { Foo { x: &1 }.y < Foo { x: &2 }.y };

So, the solution was to forbid promoting unions (since pointer to usize casts are already not promotable). But this just shifts the problem

union Foo { x: &'static i32, y: usize }
const A: usize = unsafe { Foo { x: &1 }.y };
const B: usize = unsafe { Foo { x: &2 }.y };
let x: &bool = &(A < B);

which accidentally works on stable 1.27 and does undefined behaviour. It is "fixed" on beta (and the fix will be in stable 1.28), but the fix is only to abort instead of UB.

We now need to figure out the concrete rules around this so we know when and where to error out, and when to not promote.

One such solution is unconst (similar to unsafe, but at compile-time). This means that if you do unconst things wrongly, your compiler might produce an error during monomorphization or codegen or whenever it feels like.

Some rules for unconst:

  • any use of unsafe is also unconst
  • raw pointer -> usize casts via as are unconst

What does it mean when I use unconst?

  1. anything unconst will not get promoted.
  2. unconst is not propagated past the same boundaries that unsafe isn't. So a const fn, const or static that internally uses unconst things does not show up as unconst, because the final value might be perfectly fine, even if the intermediate computation did dangerous things
  3. Using unconst to produce a const or static means you need to make sure the value is actually a value of that type. So if you have a usize, that value needs to be an integer, not an address. Similar how a &u32 needs to not be 0 or generally pointing anywhere but at a u32.

We already have this concept when you compute array lengths or enum variant discriminants. So there's definite precedent for this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions