16

The code:

#define OPPOSITE(c) (*((typeof(x) *)&(x)))

int foo(volatile int x)
{
    OPPOSITE(x) = OPPOSITE(x) + OPPOSITE(x);
    return x;
}

int bar(volatile int x)
{
    OPPOSITE(x) = OPPOSITE(x) + OPPOSITE(x);
    return x;
}

The result (-Os):

foo:
        mov     DWORD PTR [rsp-4], edi
        mov     eax, DWORD PTR [rsp-4]
        mov     edx, DWORD PTR [rsp-4]
        add     eax, edx
        mov     DWORD PTR [rsp-4], eax
        mov     eax, DWORD PTR [rsp-4]
        ret
bar:
        mov     DWORD PTR [rsp-4], edi
        mov     eax, DWORD PTR [rsp-4]
        add     eax, eax
        ret

or ARM gcc. (-O3)

foo:
        sub     sp, sp, #8
        str     r0, [sp, #4]
        ldr     r3, [sp, #4]
        ldr     r2, [sp, #4]
        add     r3, r3, r2
        str     r3, [sp, #4]
        ldr     r0, [sp, #4]
        add     sp, sp, #8
        bx      lr
bar:
        sub     sp, sp, #8
        str     r0, [sp, #4]
        ldr     r0, [sp, #4]
        lsl     r0, r0, #1
        add     sp, sp, #8
        bx      lr

https://godbolt.org/z/6z5Td9GsP

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
0___________
  • 60,014
  • 4
  • 34
  • 74
  • It looks like the cast removes the `volatile` keyword, or for some reason the `volatile` keyword is not honored for an argument, which does not make much sense anyway. What code does this generate: `int bar(volatile int x) { return x + x; }` – chqrlie Feb 09 '23 at 09:29
  • 2
    @chqrlie: Same code-gen with `#define OPPOSITE(c) (c)` https://godbolt.org/z/MWsbT5nan so this is a GCC bug: the volatile assignment isn't respected, and neither is the fact that there are two reads in the right hand side. (Unless I'm missing something about sequence points and multiple unsequenced accesses to a volatile object being UB? Nope, identical code-gen with a tmp variable: https://godbolt.org/z/r4evhT913) – Peter Cordes Feb 09 '23 at 09:31
  • 2
    You can delete this OPPOSITE thing and get the same code. – user253751 Feb 09 '23 at 09:32
  • Repeating it another time in the same compilation unit matches `bar`, so it seems the first one is special somehow. (Or the last one, for GCC6.3 for PowerPC64 https://godbolt.org/z/qnKcn7Yf5). Identical code folding gone wrong, perhaps? Yup, **`-fno-ipa-icf` makes all functions the same, as expected** https://godbolt.org/z/jK5a4j6q8 - matching the first non-buggy definition. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fipa-icf . When you report this as a GCC bug, use an MCVE without the `#define` since the code-gen is the same without it. And mention IPA ICF. – Peter Cordes Feb 09 '23 at 09:34
  • Actually this might just be UB. https://stackoverflow.com/questions/75247233/can-volatile-variables-be-read-multiple-times-between-sequence-points _However_, in that case `-std=c99` should lead to deterministic code and it doesn't. – Lundin Feb 09 '23 at 09:47
  • The bug (if it's a bug) shows up from gcc 5.1 upwards (with or without -fno-ipa-icf doesn't change anything) – Jabberwocky Feb 09 '23 at 09:48
  • 1
    @Jabberwocky Mmhm so from the change from default standard gnu90 to gnu11, which means that my comment above is likely why. Still `-std=c99 -pedantic` should remove the UB case and it doesn't. So the bug is that gcc treats volatile sequencing incorrectly in C99 mode past version 5. – Lundin Feb 09 '23 at 09:51
  • 1
    We can simply it to just `int foo(volatile int x) { x = x + x; return x; } int bar(volatile int x) { x = x + x; return x; }`. Same bug. This is UB after C11 but well-defined in C99. – Lundin Feb 09 '23 at 09:59
  • @Lundin: See my comments: same wrong code-gen in a function that uses an `int tmp` variable to do only one volatile access per statement. (Unless you use `-fno-ipa-icf`). https://godbolt.org/z/KEWE5MebM is a version of that where none of the functions would invoke UB if called. (Runtime-UB in functions that aren't called shouldn't affect functions that are called, but ruling out that source of IPA confusion is maybe helpful for GCC devs to narrow down the bug.) – Peter Cordes Feb 09 '23 at 10:03
  • 1
    @PeterCordes Ah it seems `-fipa-icf ` was introduced as per gcc 5 – Lundin Feb 09 '23 at 10:08
  • Maybe GCC would replace all calls to `bar()` with `foo()` in the code, and for some mysterious reason didn't (completely) removed `bar()`...? – Zakk Feb 09 '23 at 10:20
  • It wouldn't be a bug as such if the code folding meant it refused to use the incorrect version when generating the call. But it doesn't, check out this: https://godbolt.org/z/xW6KT8fbj. However... (head ache), if I compile that one with `-fno-ipa-icf` instead the functions are generated identical but the inlining goes bad instead. In this case volatile isn't respected by the inlining. – Lundin Feb 09 '23 at 10:24
  • @user253751 I do not ask what to do with code only why two **exactly** the same functions result in different code – 0___________ Feb 09 '23 at 11:09
  • 1
    @Zakk: `bar` has external linkage, so it must have correct code in case it is called from outside the translation unit. – Eric Postpischil Feb 09 '23 at 11:43

1 Answers1

3

You can replace the code with

int foo(volatile int x)
{
        x = x + x;
        return x;
}

int bar(volatile int x)
{
    x = x + x;
    return x;
}

And have the same effect. No other C compiler generates this effect other than GCC, so I think it's reasonable to say this is some sort of compiler bug.

  • 3
    The same buggy code-gen persists even if you avoid C11 UB by using a tmp variable to avoid unsequenced volatile accesses (within one statement). Unless you use `-fno-ipa-icf` - https://godbolt.org/z/jK5a4j6q8 - interprocedural analysis: Identical Code Folding ((https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fipa-icf). So that analysis pass is clearly part of the bug in GCC's internals. As Lundin pointed out, this was a regression in GCC 5.0. – Peter Cordes Feb 14 '23 at 10:43