3 ms·
It cannot optimize away correct sanity checks. The sanity checks that it removes are ones that attempt to detect UB after it has occurred. If you instead make s
by UncleMeat 2y ago
It cannot optimize away correct sanity checks. The sanity checks that it removes are ones that attempt to detect UB after it has occurred. If you instead make sure that checking for UB is checking before the dangerous operation then it is a correct program and the compiler won't surprise you.
- lelanthran 2y ago> The sanity checks that it removes are ones that attempt to detect UB after it has occurred. The point is that there is still value in that - your running program can at least say "oops" after the fact. You'll at least get errors when running your tests! Removing the `printf ("oops")` altogether under the guise of "well, something 3 lines above was UB, so we can delete the rest of the program" is what users are complaining about. The way the compilers go about it now, is that they don't even let you fail tests because you made an oopsie a few lines ago and now everything after that is considered safe for deletion. Look at this example: char *myvar = null; ... myvar[0] = 'A'; ... if (myvar == null) { printf ("oops\n"); } I literally want every single one of those lines emitted in the final output. 1. I don't want the 'myvar[0]' assignment removed because the compiler determined that mvar is NULL. If it's there, at least I'll get a crash before the 'oops'. If the crash doesn't come, at least I have the 'oops' to warn in testing. 2. I don't want the `myvar == null` check removed. If the crash in the assignment didn't happen, I **want** the message 'oops' printed. The compiler is clearly going against the intention of the programmer if it fails to emit code for any of those lines, because it turns a failing test into a passing one.
- UncleMeat 2y agoThe challenge is that a compiler cannot really provide consistent "after UB occurs" behavior that you can rely on for these sorts of post-facto checks without pessimizing behavior in pretty bad ways in a lot of situations. If you want to be able to observe values through invalid pointers then the compiler can't do things like store locals in registers since maybe you care about checking a value through one of these pointers. Huge mess! Like, whose to say that the line "myvar[0] = 'A';" doesn't write to the location in memory where the constant string "oops\n" is stored? How will the compiler ensure that your program does what you expect after a bogus write somewhere in memory?
- lelanthran 2y ago> How will the compiler ensure that your program does what you expect after a bogus write somewhere in memory? I'm not asking for that. I'm asking that the compiler not remove code because of a prior error!" If the program prints garbage, that's good enough to fail on a test. If the program cannot print anything at all, then so be it. What's actually* happening is that an over-eager compiler is removing something that exists. I'm asking that it not do that. I'm not asking that it ensures that the line after the error executes, I'm asking that it not be eliminated.
- gpderetta 2y ago> I'm not asking that it ensures that the line after the error executes, I'm asking that it not be eliminated. I'm not sure what 'eliminated' even mean here. If the pointed to object has been replaced by a register, there might not even be a pointer to check. I.e. there might not be any meaningful code the compiler could generate for the line. Compiler transformations attempt to preserve semantics of the code, do not attempt a faithful line-by-line translation (beyond toy compilers). If there is no semantic for a line, how can the compiler translate it? Consider this code: static inline int * get_x(void* ctx) { return (int*)ctx; } static inline int indirect( int*(*getter)(void*), void*ctx) { int * ptr = getter(ctx); if(!ptr) { return 0; } // 1 return *ptr; } int main() { int x = 10; return indirect(get_x, &x); } After optimization, the code can be (and indeed is: https://godbolt.org/z/5vb9PdnYs https://godbolt.org/z/5vb9PdnYs) transformed to a simple "return 10". There is no meaningful translation for the line at [1] as there isn't actually any pointer or memory location the pointer could be pointing to in the translated program. It doesn't even make sense to warn, as main, get_x and indirect could be in completely independent libraries and each make sense as written on its own (getter+ctx is just an hand built closure, so the code is far from non-realistic).
- lelanthran 2y ago> If the pointed to object has been replaced by a register, there might not even be a pointer to check. I dunno if that is relevant. In the example you posted, simply removing the `static inline` leaves a pointer to check. The value isn't moved to a register. We are arguing the cases when the source lines are there, but then are not emitted. In the code samples under discussion, the pointer values are still there and can be checked. Their values are not sitting in some register. > (getter+ctx is just an hand built closure, so the code is far from non-realistic). Qualifying with `static inline` is unusual, and none of the objections are using static+inlined code as samples of poor code generation. The examples being presented are a lot more real (as they are taken from existing project, not contrived to make an argument).