-
Notifications
You must be signed in to change notification settings - Fork 17
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
Promotion rules #2
Comments
Also see #6 Basically, my answer to 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); is that the sanity check should reject |
Things that are still entirely missing from the document:
@eddyb would be great if you could mention other things that restrict promotion. Ideally, the document in this repo should eventually serve as a full spec for that analysis. |
"references to other statics" is misleading. References are fine to promote, the hard problem is promoting reads from dereferences ( If Note that the limitation around |
How could the runtime value be different? CTFE does not permit mutating statics, even if they contain interior mutability. So I think that would manifest as just another way of miri failing. Is that what you mean? Also, if we enforce const safety in promoteds, we actually know that all refs are valid. We don't promote if there was a raw-ptr-deref. So I don't think miri could actually fail with a dangling pointer or out-of-bounds read. And since globals cannot be mutated, reading from them is actually guaranteed to give the same result as at run-time, the thing that would make miri fail is an attempt to write to a global. So as long as we only promote reading deref, but not assignment to a pointer, I think we should be fine. That said, I'm all in for promoting as little as possible until we have figured out that entire story a bit better. |
You can modify the static at runtime. So any CTFE value would be "out of date". |
oh I see... because for |
So, we could actually allow reading statics whose backing memory is marked immutable? Might not be worth the risk, though. |
statics can contain the address of other statics through which you can read (un-)changed statics. Even if the type does not reveal this, it feels like it would require a lot of mental overhead in all future stabilizations of const features to make sure that we don't also brick this further special case |
Yeah it would be very restricted and probably easy to screw up. Too easy. But then, given that statics cannot even be mentioned in a promoted, it does not seem clear to me that |
The only thing I can think about is that on embedded we could have a |
@RalfJung IIRC it's because
Yeah I think that's a much stronger guarantee than I was working with. |
Indeed let us not change the promotion rules while old borrowck exists. And even then we should be careful. I am more trying to understand what we could do than suggesting we actually do it. |
We now have pretty good docs of the current promotion rules; is there anything left for this issue? |
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.
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, butdid 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.
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, butis 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):So, the solution was to forbid promoting unions (since pointer to usize casts are already not promotable). But this just shifts the problem
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 tounsafe
, but at compile-time). This means that if you dounconst
things wrongly, your compiler might produce an error during monomorphization or codegen or whenever it feels like.Some rules for
unconst
:unsafe
is alsounconst
usize
casts viaas
areunconst
What does it mean when I use unconst?
unconst
will not get promoted.unconst
is not propagated past the same boundaries thatunsafe
isn't. So aconst fn
,const
orstatic
that internally usesunconst
things does not show up asunconst
, because the final value might be perfectly fine, even if the intermediate computation did dangerous thingsunconst
to produce aconst
orstatic
means you need to make sure the value is actually a value of that type. So if you have ausize
, that value needs to be an integer, not an address. Similar how a&u32
needs to not be0
or generally pointing anywhere but at au32
.We already have this concept when you compute array lengths or enum variant discriminants. So there's definite precedent for this.
The text was updated successfully, but these errors were encountered: