-
Notifications
You must be signed in to change notification settings - Fork 58
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
Does our UB have "time travel" semantics? #407
Comments
(I'm aware that I am going to ignore the questions you directly posed and generally address the broader issue) I'm very wary of this question. I commented in the meeting that I'm concerned that the actual problem here is not so much about the suitability of some semantics, but about the user's ability to debug programs. So I'm concerned that we have a sort of fundamental teaching/tooling issue. Many users learn to debug programs compiled in a debugging-friendly mode by inserting I suggested in the meeting that what users probably need is a UB-proof debugging facility. Miri is one such facility, but it can't execute all programs so we definitely need more. Sanitizers are another such partial facility. If we are going to have reorderings over externally observable effects, I think we should be able to explain how users should debug programs in the face of such reorderings. |
I want to bring up a related point that likely needs a separate issue but I fear will get shot down too quickly out of context. C compilers often come with flags to control what standard they conform to. The obvious example of this is Historically, we have shied away from such flags for fear of fragmenting the ecosystem, but perhaps this is a good place to use the technique. Turning off optimizations is obviously not the right way to get predictable behavior in the face of UB, but a flag that actually makes more things DB (at the expense of performance) might actually be a much more effective way to get many of the same benefits from Miri in the regular compiler. In this case, we could have a flag that makes all IO actions block propagation of UB. Another example along the same lines would be a flag to make all reads and writes volatile. |
That sounds like an important axis for "suitability of the semantics" to me. Making printf-debugging work is a good argument against time-traveling UB. |
I don't think there's a way to specifically tell LLVM not to perform such reorderings. But as far as I can tell, the allowed optimizations without time travel are exactly equivalent to the allowed optimizations if you assume that I/O functions might loop forever instead of returning. So in theory, it should just be a matter of removing the In practice, though, I don't think I/O functions or calls ever get annotated with Nor can LLVM deduce As for volatile accesses, LLVM already treats volatile stores as potentially not returning, though not so for volatile loads, per LangRef. In sum, with the exception of volatile loads, rustc already doesn't do time travel in practice. If anything, the question is how much performance could be gained by adding That said, if we wanted to officially guarantee the absence of time travel, we'd have to consider the possibility that Clang someday decides to add an attribute for |
Isn't this what debug assertions are already doing with eg. rust-lang/rust#51713? I'm specifically thinking of rust-lang/rust#98112 here, which added assertions on the language level, not just in the library. |
I do not think so. Per @digama0's comment, what I'm doing is not like |
@comex from the llvm langref
that to me reads that all volatile is "will return" semantically. |
@Lokathor the very next paragraph is:
|
If a program performs an externally observable action (such as printing to stderr or volatile memory accesses) followed by UB, do we guarantee that the action occurs?
The fact that C does not guarantee this is known as "time-traveling UB" and it can be quite perplexing. It arises when the compiler reorders potentially-UB operations up across
mustreturn
functions:If the read is moved up across the print, we get a pagefault before printing, i.e. we have time travel.
From the theory perspective, this is a reasonably clear situation: either we can move potentially-UB code up across externally observable operations, or we guarantee that UB is properly sequenced wrt externally observable operations. We cannot have both. (In principle we can decide this on a per-operation basis, e.g. we could require proper sequencing for stdout/stderr but allow reordering for volatile accesses.)
So how bad is the cost of not doing such optimizations? Is that even something we can currently evaluate, or does LLVM just always perform such reorderings?
(I am assuming here a general consensus that potentially-UB operations can be reordered wrt not externally observable operations. This can only be observed with a debugger and IMO clearly falls on the side of the optimizations having too may benefits to do anything else.)
The text was updated successfully, but these errors were encountered: