Hacker News new | ask | show | jobs
by Ontonator 718 days ago
Do you have a specific reference for this? I’d love to know more.
1 comments

Same here, would love to educate myself on this with specific standard references
You can find the C standard for C23 here. https://www9.open-std.org/JTC1/SC22/WG14/www/docs/n3220.pdf (technically it is the draft for the next version, but only has a footnote changed). The definition for UB is (not changed with C23):

"undefined behavior - behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements"

It says "for which" and not "for the whole program". So the "interpretation" that a program becomes invalid or other requirements are affected was never supported by the text in the C standard. This was justified by some with arguments such as: "no requirements" includes behavior that can go back in time or otherwise break the rules of the real world. Of course, if you start to interpret a standard text in this creative way, you can justify anything. A reasonable interpretation must assume some general properties of the application domain it applies to (and in C there also more specific rules about how requirements are to be interpreted in 5.1) and must assume that wording such as "for which" has actual meaning that needs to be taken into account and can not simply be ignored. In C23 "Note 3" was added that aims to clarify that such nonsensical interpretations are not intended:

"Note 3 to entry: Any other behavior during execution of a program is only affected as a direct consequence of the concrete behavior that occurs when encountering the erroneous or non-portable program construct or data. In particular, all observable behavior (5.1.2.4) appears as specified in this document when it happens before an operation with undefined behavior in the execution of the program"

Does this means that compilers cannot in general reorder non-side-effects operations across side effects, even if those operations wouldn't be globally visible if not for UB? Alternatively, is UB ordered with side effects? What's the ordering of UB with regard to other thread operations? Does it guarantee sequential consistency? I guess happens-before is guaranteed only if something else would guarantee happens before, but it means further constraint on ordering of, for example, potentially faulting operations across atomic operations.

Ie:

   int ub(int idx, _Atomic int* x)) {
       char y[1000];
       int r = *x;    // 2
       r+= y[idx];    // 1
       return r;
   }
Statements 1 and 2 can't be reordered as any UB in accessing y[idx] is sequenced-after any side effect that happens-before 2, even if y is a purely local, non-escaping variable. This puts constraints on register allocation for example.

This opens a big can of worms.

edit: from a few quick tests GCC seems quite good at preserving ordering of potentially faulting instructions across side effects, even when reordering would be profitable (for example hoisting operations out of loops). It might be a posix requirement in practice because signals make them "visible".

edit2:

ok, this "fails":

  extern volatile int x;
  int ub(int d, int c) {
    int r;
    for (int i = 0; i < 100; ++i) {
      r+= x;   // 1
      r+= d /c; // 2
    }
    return r;
  }
GCC -O3 will hoists out of the looop the potentially faulting (and expensive) division at [2] before the volatile load at [1] (which is a side effect). Would you consider this a valid transformation or is it time-traveling UB?

Potentially this could be fixed by unrolling out the first iteration of the loop and preserving the first volatile access above the division, which can then be cached. This would also help with the variant where the volatile access is replaced by a function call that currently gcc doesn't optimize.

The can of worms is not so big actually. In general, observable behavior is only I/O and volatile accesses. This is not about side effects in general (which can be optimized according to the "as if" rule). So many things can still be reordered vs potentially trapping operations. Also potentially trapping operations can be reordered. For multi-threading we have "happens before" relationship, so a natural interpretation is that everything which happens before the UB is safe.

The reordering of a volatile access after a potentially trapping operation is not conforming. I think it is an important property of volatile that it prevents this optimization, so I hope that GCC will be fixed eventually. A potentially trapping operation can also not be hoisted above a function call, and compilers that did this all got fixed in the mean time.

https://developercommunity.visualstudio.com/t/Invalid-optimi...