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
Do aliasing requirements carry over from const-time to run-time? #424
Comments
My initial reaction is indeed "obviously yes". I think it is less about the read-only-ness itself but rather aliasing information in general: what kind of provenance do pointers carry over across the const-time -> run-time barrier? The natural answer is "all of it", in which case we get this behavior (the code is UB). The alternate natural answer, "none of it", means that these pointers must go through some kind of exposing process to be usable at all, which might cause other undesirable consequences. Just to gauge where MiniRust and Miri sit on this spectrum, what happens if you use instead: static PTR: Foo = Foo(std::ptr::invalid_mut(&FOO as *const i32 as usize)); (To answer my own question: it's currently disallowed because |
Given that That said, I've heard some experts push back against the "textually replace" model for consts, so grain of salt and such. Still, my line of thinking would also point to "obviously yes" being the answer. |
Sure, for an easy explanation is seems best to make this UB.
What I am wondering about is whether that should be just an "explanation model" or whether it is worth going through the effort of actually making this UB.
(In Miri it will be architecturally hard to make this UB, but that is not my concern. It will be architecturally hard to even detect aliasing violations within a const eval execution.)
|
Without putting too fine a point on it, while I appreciate having Miri there to help check for UB, I don't like the implication that implementation details of Miri should drive the definition of UB. Unless there is a reason that this is not implementable, I think we should be allowed to keep the focus on the theoretical model and explaining that (or simplified versions of it). Put another way, I would not expect an implementation in Miri to be a blocking concern in FCP for aspects of the formal model. (Note that one could very well say the same thing about stacked borrows in the first place: it is architecturally hard to make SB aliasing violations detectable, but we did it anyway.) |
I explicitly said Miri is *not* my concern. How do you read that as implying that Miri should drive the def.n if UB? I mentioned this because people might be inclined to probe Miri and I didn't want anyone to reach false conclusions.
|
Maybe you could clarify this sentence then?
Detect in what? Minirust, Miri or rustc const eval? (Or on paper.) |
Turning a promoted pub const FOO: &i32 = &0; I'd full expect to be allowed to place In the case of starting with a |
@chorman0773 — your example is different, as it's purely using I apparently was mistaken, since this example compiles on stable, but I previously thought that reference-to-mutable was necessarily forbidden from escaping const evaluation and making it to a (Because of this, it's possible that the ordering of evaluation of From an opsem specification perspective, I would expect that "yes, this causes UB" is the easier answer, in addition to it likely being more intuitive. For it to be UB, all you need to say is that the borrow tree state carries over from const evaluation to runtime. For the answer to be "no, this isn't UB," there needs to be some sort of "serialization" stage between const evaluation and runtime evaluation, where pointers/references in Mitigating the extra step, though, is that we do necessarily already have an observable split between const evaluation time and runtime, since const panics block compilation up front instead of happening at runtime when encountered. |
I was building up the "obvious" example. By my intuition, there isn't any difference between the two - both generate a Frozen tag at const time, and yield a pointer with that tag to runtime. I don't see a difference in how that tag comes about w/o introducing special semantics, which I'm unsure are necessary. Or rather, I believe the best way to ensure that a promoted const/immutable static can't be mutated through a pointer is to simply carry the TB/SB tag from const eval into the value of the const/static initialized with it. |
Detect in Miri. But as I said that is not my concern for this issue. For MiniRust we don't have a plan yet for how to evaluate constants ourselves (rather than have rustc eval them), but that's what we will have to do and then non-aliasing within a const eval instance is easily enforced. The trouble is the next step: we produce a 'global' to be used by runtime code (and even other const eval), where we can only symbolically refer to other globals. Currently this happens via "globalid-offset" pairs. To enforce aliasing across the boundary between different interpreter instances we'd have to also put stack/tree data into this. We'll need "symbolic pointer tags" (to be turned into concrete tags when the AM is initialized) and a bunch of surrounding infrastructure. It's not impossible but it's a lot of machinery, for questionable benefit. @chorman0773 I view your example as very different. We will have a notion of when these "globals" are read-only (meaning they can be put into read-only memory and any write access is UB); we "just" need to figure out the exact rules for when they are marked as read-only, but for promoteds it seems pretty clear.
This is very much not the easier answer. You cannot just carry over that state: imagine 2 constants being evaluated independently, and now at runtime we use both of them -- they might have used the same borrow tag, so we need to re-map all borrows tags to ensure they remain distinct! We do this for allocation IDs via symbolic "global IDs" in MiniRust; we'd need all the same infrastructure for borrow tags as well. So carrying over the tag is definitely a non-starter. The only option is to build complicated machinery that can reconstruct an isomorphic (but not identical) state when the runtime AM starts. We should have good motivation for going through this. Just specifying when a piece of global memory is read-only is a lot simpler. |
I think the answer to this question is blocked on some bigger design questions:
Trying to answer the question about how aliasing data is reinterpreted at runtime seems quite far along in the tech tree compared to these more basic questions that we still don't have a solid model for, so I would prefer to shelve the question until then, so that we can have a framework in which to consider what the options even are. |
Agreed, it probably makes sense to start with the broader framework here. I have thoughts on that but haven't ever really written them down, and no idea for what part of that we have consensus. |
If a pointer is created as read-only during one program execution stage, is that restrictions inherited by future stages? IOW, is this code UB?
If this would be running in a single instance of the AM then clearly this is UB. But does the alias information carry over from the compiletime AM to the initial state of the runtime AM?
Maybe thr answer is "obviously yes", but it doesn't seem so obvious for me. First of all, we'd need to set up extra machinery to even be able to put such alias information into the initial state of the runtime AM (i.e., even once Stacked/Tree Borrows are implemented for MiniRust, this won't be UB in MiniRust since the initial value of a "global" can't express that
PTR
is read-only). We could of course add that machinery. Then the question becomes: what is the benefit of doing so? We need aliasing restrictions to perform analyses and reasoning on references that alias with unknown code; with the results of const-eval being completely known to later stages, there's no clear benefit. Sure we can construct artificial examples, but is it worth it?The text was updated successfully, but these errors were encountered: