I noticed that clang and gcc optimize away the construction of or assignment to a volatile struct
declared on the stack, in some scenarios. For example, the following code:
struct nonvol2 {
uint32_t a, b;
};
void volatile_struct2()
{
volatile nonvol2 temp = {1, 2};
}
Compiles on clang to:
volatile_struct2(): # @volatile_struct2()
ret
On the other hand, gcc does not remove the stores, although it does optimize the two implied stores into a single one:
volatile_struct2():
movabs rax, 8589934593
mov QWORD PTR [rsp-8], rax
ret
Oddly, clang won't optimize away a volatile store to a single int
variable:
void volatile_int() {
volatile int x = 42;
}
Compiles to:
volatile_int(): # @volatile_int()
mov dword ptr [rsp - 4], 1
ret
Furthermore a struct with 1 member rather than 2 is not optimized away.
Although gcc doesn't remove the construction in this particular case, it does perhaps even more aggressive optimizations in the case that the struct
members themselves are declared volatile
, rather than the struct
itself at the point of construction:
typedef struct {
volatile uint32_t a, b;
} vol2;
void volatile_def2()
{
vol2 temp = {1, 2};
vol2 temp2 = {1, 2};
temp.a = temp2.a;
temp.a = temp2.a;
}
simply compiles down to a simple ret
.
While it seems entirely "reasonable" to remove these stores which are pretty much impossible to observe by any reasonable process, my impression was that in the standard volatile
loads and stores are assumed to be part of the observable behavior of the program (in addition to calls to IO functions), full stop. The implication being they are not subject to removal by "as if", since it would by definition change the observable behavior of the program.
Am I wrong about that, or is clang breaking the rules here? Perhaps construction is excluded from the cases where volatile
must be assumed to have side effects?