Search code examples
c++compiler-optimizationclang++

Tell Optimizer (O2) two Pointers are the 'same' (LLVM Linux Armv7)


I have a class that has 2 pointers to a external memory p_Data and p_DataWrite. Most of the time, both of those pointers point to the same memory and are used to read said memory (p_Data) or write said memory (p_DataWrite). I now have a case in which I write the memory (using p_DataWrite), then read the memory (using p_Data). When I compile this with clang++ for the ARMv7 platform for Linux using O2 I get the effect, that the memory gets read first und written afterwards. I tried using the p_Data pointer to write the memory and the effect is gone. That is not a option for the project.

I see the effect only for ARMv7 Linux with at least O2. I also build my project for x86_64 Linux and for x64 Windows and can compile with O2 and above without this issue.

How can I tell the compiler/optimizer that those two pointers are the 'same' and it is not allowed to change the execution order?

Edit Add Example Code:

        *((unsigned int *)IN_Class.p_DataUserWrite) = 0x12345678;

        unsigned short In_Word = *((unsigned short *)IN_Class.p_DataUser + 0);
        unsigned short Out_Word = (unsigned short)((In_Word >> 8) | (In_Word << 8));

        In_Word = *((unsigned short *)IN_Class.p_DataUser + 1);

        *((unsigned short *)OUT_CLass.p_DataUserWrite + 1) = Out_Word;
        Out_Word = (In_Word >> 8) | (In_Word << 8);
       
        *((unsigned short *)OUT_CLass.p_DataUserWrite) = Out_Word;

translates to

;         *((unsigned int *)IN_Class.p_DataUserWrite) = 0x12345678;
   1f2b8: e5923034      ldr r3, [r2, #0x34]
   1f2bc: e5920030      ldr r0, [r2, #0x30]
;         *((unsigned short *)OUT_CLass.p_DataUserWrite + 1) = Out_Word;
   1f2c0: e5911030      ldr r1, [r1, #0x30]
;         unsigned short In_Word = *((unsigned short *)IN_Class.p_DataUser + 0);
   1f2c4: e1d320b0      ldrh    r2, [r3]
;         In_Word = *((unsigned short *)IN_Class.p_DataUser + 1);
   1f2c8: e1d330b2      ldrh    r3, [r3, #2]
;         *((unsigned short *)OUT_CLass.p_DataUserWrite + 1) = Out_Word;
   1f2cc: e6bf2fb2      rev16   r2, r2
   1f2d0: e1c120b2      strh    r2, [r1, #2]
   1f2d4: e3052678      movw    r2, #0x5678
   1f2d8: e3412234      movt    r2, #0x1234
;         *((unsigned int *)IN_Class.p_DataUserWrite) = 0x12345678;
   1f2dc: e5802000      str r2, [r0]
;         *((unsigned short *)OUT_CLass.p_DataUserWrite) = Out_Word;
   1f2e0: e6bf0fb3      rev16   r0, r3
   1f2e4: e1c100b0      strh    r0, [r1]
;     }
   1f2e8: e12fff1e      bx  lr


Solution

  • Compile with -fno-strict-aliasing.

    In *((unsigned int *)IN_Class.p_DataUserWrite) = 0x12345678; you write to the address using the type unsigned int. This differs from the type used to access the address in other lines, where unsigned short is used.

    Accessing the same memory with two different types other than as listed in C++ 2020 7.2.1 [basic.lval] 11 violates the aliasing rule in that paragraph. Then the behavior is not defined by the C++ standard. The import of this is that the compiler does not have to treat an access as unsigned int and an access as unsigned short as if they might access the same memory. So the optimizer is free to rearrange this code as if the addresses were different.

    Clang has a feature to tell the compiler to support aliasing memory with different types, and the -fno-strict-aliasing switch requests that.

    Alternatively, you could modify the code to use only unsigned short to access the memory, separating the write of 0x12345678 into two writes of an unsigned short.