4 ms·
No, that's not the right check at all. The majority of those optimizations aren't taking advantage of undefined behaviour. Neither GCC nor clang provide a simp
by CJefferson 10y ago
No, that's not the right check at all.
The majority of those optimizations aren't taking advantage of undefined behaviour. Neither GCC nor clang provide a simple way of getting "no undefined behaviour" optimisations.
Unfortunately, in practice it's often hard to define what that would actually mean. For example, fairly simple optimiations like optimising:
int silly(int i) {
int y = 3;
int x[4];
x[i] = 4;
return y;
}
To just:
return 3;
Technically relies on undefined behaviour -- if you compile this without optimisation then on my machine passing silly(7) returns 4 (as x[7] happens to write over y). So the question becomes, where is the "line" we want to draw on not allowing undefined behaviour optimisations? It turns out unfortunately in practice everyone seems to have a different line on what should be allowed.
- gpm 10y agoOverflow checks. Passing silly[7] should do something predictable, i.e. abort the program. This is, for example, what rust does. Of course studies have been done on how expensive overflow checking is. IIRC around 5% overhead in the most extreme examples.
- millstone 10y agoThis is an interesting example. The store to x is dead and ought to be totally elided, but Rust will still emit an "overflow check" for an array that does not exist. This overflow check dominates the cost of the function. If the optimizer determines a store is dead and can elide it, should it also elide the overflow checks? If "no," then it seems we've unnecessarily hamstrung the optimizer. If "yes," then whether a program panics is optimization-level dependent.
- petertodd 10y agoedit: re-reading your comment I suspect we're in violent agreement. :) > The store to x is dead and ought to be totally elided Yet, it's a clear sign of a bug in the program; the fact it happened to be elided in that case doesn't change the fact that it's almost certainly a bug that I want to know about. Heck, earlier today I wrote a Rust function along these lines: fn foo<A, B>(a: &A) -> &B { assert_eq!(size_of::<A>() == size_of::<B>()); unsafe { // do horrible low-level things } } The assertion is there to make sure that two generic type parameters that should have an identical size actually do. If they do in optimized builds the compiler knows the assertion is true and completely elides the assertion itself. If the sizes aren't identical, the assertion isn't elided and I'll quickly find the bug when the program crashes(1). I definitely wouldn't want that assertion to get elided just because the "horrible low-level things" happened to be categorized as undefined behavior! 1) Of course, a sufficiently smart compiler could detect that the assertion was false at compile time in this simplified example.
- nwmcsween 10y agoHow does bounds checking do with vectorization? I bet it's not 5%.
- Ded7xSEoPKYNsDd 10y agoThese people have done this work (and it's great!), and in their self-selected benchmarks programs they claim about 16% overhead: https://www.cis.upenn.edu/acg/softbound/ https://www.cis.upenn.edu/acg/softbound/ This is only memory safety, not using other optimizations related to undefined behavior will add a bunch more overhead.