Consider the following C program:
typedef struct { int x; } Foo;
void original(Foo***** xs, Foo* foo) {
xs[0][1][2][3] = foo;
xs[0][1][2][3]->x = 42;
}
As far as I understand, per the C standard Foo** cannot alias Foo* etc, as their types are not compatible. Compiling the program with clang 14.0 and -O3 however results in duplicate loads:
mov rax, qword ptr [rdi]
mov rax, qword ptr [rax + 8]
mov rax, qword ptr [rax + 16]
mov qword ptr [rax + 24], rsi
mov rax, qword ptr [rdi]
mov rax, qword ptr [rax + 8]
mov rax, qword ptr [rax + 16]
mov rax, qword ptr [rax + 24]
mov dword ptr [rax], 42
ret
I would expect an optimising compiler to either:
(A) Assign to x on foo directly and assign foo to xs (in any order)
(B) Perform address calculations for xs once and use them for assigning foo and x.
Clang correctly compiles B:
void fixed(Foo***** xs, Foo* foo) {
Foo** ix = &xs[0][1][2][3];
*ix = foo;
(*ix)->x = 42;
}
as follows: (actually turning it into A)
mov rax, qword ptr [rdi]
mov rax, qword ptr [rax + 8]
mov rax, qword ptr [rax + 16]
mov qword ptr [rax + 24], rsi
mov dword ptr [rsi], 42
ret
Interestingly gcc compiles both definitions into A. Why is clang unwilling or unable to optimise the address calculation in the original definition?