Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm starting to think we need a way to allow UB-based optimisations on a per-function or per-block basis, with the rest of the code being compiled with the simplest mental model. It's getting a bit hard to reason about whether your code is actually safe or not, it would be better to compile most code as if all pointers can alias, integers wrap around, accessing nullptr is allowed, etc. (subject to per-platform quirks) Then we can mark the hot functions as candidates for better optimisation. Maybe other than an attribute we could add assertions to the code, e.g. assert_no_overlap(ptr1, size1, ptr2, size2) -- assert that the range [ptr1, ptr1+size1) does not overlap [ptr2, ptr2+size2). That could then optionally be compiled into an actual runtime assertion to help catch UB at runtime.

Ultimately the C (and C++) memory model is meant to help us write fast, safe software. Developers must be able to reason about the semantics of their code. Optimising short parts of the program that can be reasonably checked by a person for not invoking UB is probably the right way and will result in fast software with fewer bugs due to programmer error.

Edit: Not sure why people are downvoting this, it's like there's some UB religion out there. Do you people want your programs to crash due to the compiler doing surprising things?



> Do you people want your programs to crash due to the compiler doing surprising things?

I obviously can't speak for everyone who defends this sort of compiler behavior, but my preferred solution is for the compiler to continue performing these kinds of optimizations while also providing better static analysis and sanitizer tools to catch problems earlier, even if that involves extending or replacing the language to make that possible.

Expecting everyone who writes C or C++ to do this kind of reasoning in their head and perform these optimizations manually (or drop their performance back toward -O0) just sounds like it would make things worse.


This already exists. gcc has optimization pragmas and function attributes that allow optimization to be set on a per-function level. However, they don't really work as expected, mainly because inlining exists. Example:

  int foo(void) { return 42; }
  int bar(void) { return foo() + foo(); }
If the whole file is compiled as -O0, foo and bar must be compiled as-is. If the whole file is compiled as -O2, bar will be optimized to "return 84;". But what if foo is compiled as -O0 and bar is compiled as -O2? Can bar still be optimized to "return 84;"? What about "return 42+42;"? Can it do "int x = foo(); return x+x;"? What about "int x = foo(); if (x == 42) return 84; else return x+x;"? All of these are permitted in -O2 if gcc thinks it's a good idea, but in our "mixed mode" the semantics are unclear. It might be theoretically possible to come up with a consistent definition; it might start with "functions specified as -O0 cannot be inlined or have other functions inlined into them", but there are still more things to consider, like the behavior of global memory, thread-local storage, floating-point environment, and so on (what if one or more functions is static inline or __attribute__((always_inline))?).

The real solution with current gcc is to simply compile different files ("translation units") with different optimization flags if you want. This has always been supported. Unfortunately, it comes with a potentially significant performance penalty; the whole point of LTO is to avoid this performance penalty, but this once again returns the issue of the semantics of mixing functions with different optimization levels.


> Ultimately the C (and C++) memory model is meant to help us write fast, safe software

Fast, maybe. Safe, not really.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: