4 ms·
That's a terrible maxim. You'd forbid the Rust compiler from performing as simple an optimisation as changing: fn foo() { let x = 0; bar();
by ivanbakel 3y ago
That's a terrible maxim. You'd forbid the Rust compiler from performing as simple an optimisation as changing:
fn foo() {
let x = 0;
bar();
return x;
}
to
fn foo() {
bar();
return 0;
}
(which is valid, because the binding of `x` is immutable, and it's UB to mutate it in `bar`, even if you can guess the stack pointer.)
Optimising compilers require the ability to make assumptions about your code - that's the basis by which program transformations are valid. Even something as simple as constant loop unrolling would be forbidden by your rules, since your code can (invalidly) choose to try to mess with loop variables in a way the compiler can't spot.
- kazinator 3y agoThis kind of optimization is a main motivator for lexical scope, going back to 1960-something, so we make an exception for it and educate the users: your local variables are aggressively optimized: subject to allocation in registers, constant propagation and folding. We are not relying on specifics about bar(), just the general assumption that up until the return 0, the program hasn't done anything incorrect which interferes with what we want to do. It's a different reasoning from "this loop cannot terminate because i++ never goes negative, so we can cheerfully remove the code after it, including the return instruction."
- uecker 3y agoWhy is it different? "program hasn't done anything incorrect" includes not overflowing i. And being able to even put variables in register relies on such assumptions in a very similar as certain loop transformations rely on the assumption that the loop terminates before i overflows.
- kazinator 3y agoThe language should make it impossible to access x through the stack unless the programmer goes out of their way to perpetrate fraud. It's fair for the optimizer to assume that fraud hasn't been perpetrated. If it's easy to overflow integer addition, then that must be regarded as an accident. Assuming absence of accidents is a poor default in ways that assuming the absence of fraud isn't. First make it so that perpetrating integer overflow is inconvenient, so that the programmer has to go out of his way to request it. If that has been done, then sure, blindly assume that i + 1 is greater than i.
- uecker 3y agoInteger overflow is relatively easy to detect and protect against at run-time using -fsanitize=signed-integer-overflow. (yes, needs testing. Doing it reliable at compile-time is hard.) But compilers also offer various options on how to deal with overflow. The users choose -O3 over other options. ISO C does not favor one over the other. But making it defined behavior that wraps would turn it into subtle correctness bugs which one can not easily screen for.
- kazinator 3y agoIt's possible to just leave it undefined, without introducing unwarranted assumptions, such as that i + 1 is positive if i is positive, made in the absence of any assurance that i < INT_MAX.
- uecker 3y agoThat a computed expression i + 1 can be assumed to be positive for non-zero i follows mathematically from overflow being undefined.
- ivanbakel 3y ago>We are not relying on specifics about bar(), just the general assumption that up until the return 0, the program hasn't done anything incorrect which interferes with what we want to do. That's not qualitatively any different. You're still describing a class of behaviour (messing with local variables not in the same scope) that the program isn't allowed to do, because it breaks the compiler's view of the code. That behaviour is UB. What do you think separates "good" assumptions from "bad" ones?
- kazinator 3y ago> What do you think separates "good" assumptions from "bad" ones? Rarely to never wrong vs often wrong.
- UncleMeat 3y agoSure you are. You are relying on the fact that bar is not allowed to take the address of an arbitrary object on the stack, perform some arithmetic on it, and mess with the contents of the previous stack frame. This is not actually different reasoning than what you describe. I encourage you to try to formalize what you are saying. You'll find that it is remarkably hard to distinguish the two cases, especially if you understand the steps the compiler takes to get to these changes.
- deleted 3y ago[deleted]
- kazinator 3y agoIt's very easy to distinguish the cases by division into "fraud" versus "accident". When optimizing, assume absence of fraud, but not accident. If, in the given language, it is dead easy for bar() to manipulate x by accident, and frequently occurs due to that being a pitfall in that language, then it would be stupid for the language to have advanced optimizations over local variables. Unless the implementors are confident that they can diagnose almost every instance of the pitfall.
- UncleMeat 3y ago> It's very easy to distinguish the cases by division into "fraud" versus "accident". When optimizing, assume absence of fraud, but not accident. I do not believe that this is true. If you can do it precisely, that'd be a significant contribution to the community. If you can it precisely in a way that doesn't create a massive overhead because of tracking object lifetimes explicitly then that'd be an incredible contribution to the community.