3 ms·
> I think there's room for an approach that says "if you stray beyond these rules, the real processor will decide your fate" without the "electric fence of deat
by BeeOnRope 8y ago
> I think there's room for an approach that says "if you stray beyond these rules, the real processor will decide your fate" without the "electric fence of death" approach currently taken by UB. I might be wrong to call this "implementation defined", but I think that's where it best fits in.
How is this different from -fwrapv? The real processor implements the -fwrapv behavior, so it seems like the same thing.
If it's something different, how is it different than the current behavior of "compiler does whatever it wants and then the CPU executes the instructions"?
Maybe a specific example would help?
I think a key problem is that you are jumping ahead to "the real processor will decide your fate" - but that's not the interesting part. We all understand how real processors work (they wrap) - the whole question is what the language semantics of signed values are, because before you get to the processor, you have to compile the code. Even before that you have to write the code, and you want to know what happens when you do basic math on integers. If you want to invoke the specific processor behavior as part of the definition of how integers work, you also need to provide some kind of mapping from the source to machine operations (hint: I think -fwrapv behavior is the best or only way to do this practically).
> I don't want is for it to be a guaranteed no-op. Nor do I want it the compiler to eliminate code based on the assumption that the second call never happens. I want it to warn if it can (which can be a fatal warning if desired) but to always generate assembly that makes the potentially crashing call. My contract with the compiler is limited to determinism of the assembly it generates on my behalf, and does not include any guarantees or restrictions on the runtime behavior.
I see. What you basically want is a non-optimizing compiler, or a compiler that does only a limited amount of optimization following very strict rules (perhaps akin to something along the lines of how volatile is treated) about what can be optimized.
I think it is actually much more complicated than you would think (because the existing model of "as-if execution preserving required side-effects" is really very simple and clean).
More to the point, I don't think you'll ever get it [3] and you might not like what you get. 90% of optimization is eliminating code. The compiler eliminates function calls, assignments, math, allocations, jumps, branches, copies, constructors, and the list goes on forever. Elimination is the name of the game. Almost all of the time the result produced is indistinguishable from what you imagine the abstract machine doing, even though the combination of the compiler and real machine it is doing nothing like that at all.
These cases that get the press are notable for their exceptionality - at the intersection of a mismatch between what the standard says and how people imagine the language to work, and a similar mismatch between how people believe a compile (should?) work and how it does, and probably also a mismatch between the priorities of the compiler writers (performance on benchmarks) and users (correct code).
> My worry is that you (and others) don't consider this to be a standards compliant interpretation of "implementation defined", nor can it be "undefined behavior" if the compiler also reasons that UB can be assumed to never occur.
As above I'm not 100% clear on what you are suggesting, but I think it could be implementation defined as long as you can explain it (-fwrapv qualifies easily). After all, implementation defined has to be _defined_ in some way, right?
It could _definitely_ be UB, since UB can be whatever you want. A compiler could implement your suggestion right now and it would be within the rules. That just leaves us back where we are today though, right? Unknown or unexpected behavior in the presence of UB behavior.
I think there is a misconception that the "compiler assumes UB never occurs and removes all code that leads to UB". I think it's more accurate to say the compiler (in principle) just treats UB in the way that is most convenient (fast?) for the current context. In the specific case of a basic block that _always_ executes UB (e.g., references a pointer that is known to be not NULL), the most convenient/fast thing is probably to prune the code and paths that lead to this block - this type of thing happens all the time in non-buggy programs due to inlining, generic code, macros, auto-generated code, other optimization passes, etc.
In many other cases however, the most convenient thing to do is just to emit the instruction (or instruction sequence) that behaves in the required way for non-UB code, and behaves "as it will" on the hardware for UB code, like all the stuff around shifts, division by zero and even various builtins and intrinsics. The compiler doesn't go out of its way to remove the UB code, it just does what is fastest.
Nobody complains about the latter behavior though, but of course it leads to bugs all the same - but people blame the programmer, not the compiler in this case, because the result is more "obvious" (I think).
---
[3] The niche for a compiler that executes what you write more or less verbatim is just too small (beyond what you already get with -Og or -O0 or whatever): people write in C/C++ for speed, if you wanted something slower you have many better, safer options available.