Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Volatile not working as expected

Consider this code:

struct A{ 
  volatile int x;
  A() : x(12){
  }
};

A foo(){
  A ret;
  //Do stuff
  return ret;
}

int main()
{
  A a;
  a.x = 13;
  a = foo();
}

Using g++ -std=c++14 -pedantic -O3 I get this assembly:

foo():
        movl    $12, %eax
        ret
main:
        xorl    %eax, %eax
        ret

According to my estimation the variable x should be written to at least three times (possibly four), yet it not even written once (the function foo isn't even called!)

Even worse when you add the inline keyword to foo this is the result:

main:
        xorl    %eax, %eax
        ret

I thought that volatile means that every single read or write must happen even if the compiler can not see the point of the read/write.

What is going on here?

Update:

Putting the declaration of A a; outside main like this:

A a;
int main()
{  
  a.x = 13;
  a = foo();
}

Generates this code:

foo():
        movl    $12, %eax
        ret
main:
        movl    $13, a(%rip)
        xorl    %eax, %eax
        movl    $12, a(%rip)
        ret
        movl    $12, a(%rip)
        ret
a:
        .zero   4

Which is closer to what you would expect....I am even more confused then ever

like image 229
DarthRubik Avatar asked May 07 '16 01:05

DarthRubik


1 Answers

Visual C++ 2015 does not optimize away the assignments:

A a;
mov         dword ptr [rsp+8],0Ch  <-- write 1
a.x = 13;
mov         dword ptr [a],0Dh      <-- write2
a = foo();
mov         dword ptr [a],0Ch      <-- write3
mov         eax,dword ptr [rsp+8]  
mov         dword ptr [rsp+8],eax  
mov         eax,dword ptr [rsp+8]  
mov         dword ptr [rsp+8],eax  
}
xor         eax,eax  
ret  

The same happens both with /O2 (Maximize speed) and /Ox (Full optimization).

The volatile writes are kept also by gcc 3.4.4 using both -O2 and -O3

_main:
pushl   %ebp
movl    $16, %eax
movl    %esp, %ebp
subl    $8, %esp
andl    $-16, %esp
call    __alloca
call    ___main
movl    $12, -4(%ebp)  <-- write1
xorl    %eax, %eax
movl    $13, -4(%ebp)  <-- write2
movl    $12, -8(%ebp)  <-- write3
leave
ret

Using both of these compilers, if I remove the volatile keyword, main() becomes essentially empty.

I'd say you have a case where the compiler over-agressively (and incorrectly IMHO) decides that since 'a' is not used, operations on it arent' necessary and overlooks the volatile member. Making 'a' itself volatile could get you what you want, but as I don't have a compiler that reproduces this, I can't say for sure.

Last (while this is admittedly Microsoft specific), https://msdn.microsoft.com/en-us/library/12a04hfd.aspx says:

If a struct member is marked as volatile, then volatile is propagated to the whole structure.

Which also points towards the behavior you are seeing being a compiler problem.

Last, if you make 'a' a global variable, it is somewhat understandable that the compiler is less eager to deem it unused and drop it. Global variables are extern by default, so it is not possible to say that a global 'a' is unused just by looking at the main function. Some other compilation unit (.cpp file) might be using it.

like image 120
Sami Sallinen Avatar answered Oct 26 '22 23:10

Sami Sallinen