Specifically, consider the case of adding a spurious load at the top of a function:
void test(noalias int *ptr1, dereferenceable(4) int *ptr2, bool b) {
if (b) { *ptr1 = 42; }
// ...
}
becomes
void test(noalias int *ptr1, dereferenceable(4) int *ptr2, bool b) {
int val = *ptr2; // add spurious load
if (b) { *ptr1 = 42; }
// ...
}
Clearly this transformation should be allowed. But if b is true then the second program has a noalias conflict at the write to ptr1 – the load from ptr2 is fine and I don’t think there is a way for this load to return poison, since at this point in the execution one cannot even know yet whether this load will be part of a load-store noalias conflict.
Therefore the 2nd program has UB (if b is true), and therefore for the transformation to be correct the original program must have already had UB (if b is true).
EDIT: There also is a fundamental problem, I think, with the idea of making conflicting loads return poison instead of causing insta-UB. Consider something like
void test(noalias int *ptr1, noalias int *ptr2) {
*ptr1 = 42;
int val = *ptr2;
}
Let’s say the two pointers alias. Then val will be poison but that’s fine. Now let’s recorder the two operations:
void test(noalias int *ptr1, noalias int *ptr2) {
int val = *ptr2;
*ptr1 = 42;
}
Now again the load cannot really return poison since the conflicting write has not happened yet. Therefore we only get a conflict at the store, and that causes UB. Therefore the reordering turned a UB-free program into one with UB.