<html>
<head>
<base href="https://bugs.llvm.org/">
</head>
<body><table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Bug ID</th>
<td><a class="bz_bug_link
bz_status_NEW "
title="NEW - Wrong optimizations and missed optimizations involving pointers to subobjects"
href="https://bugs.llvm.org/show_bug.cgi?id=38108">38108</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>Wrong optimizations and missed optimizations involving pointers to subobjects
</td>
</tr>
<tr>
<th>Product</th>
<td>clang
</td>
</tr>
<tr>
<th>Version</th>
<td>6.0
</td>
</tr>
<tr>
<th>Hardware</th>
<td>PC
</td>
</tr>
<tr>
<th>OS</th>
<td>Linux
</td>
</tr>
<tr>
<th>Status</th>
<td>NEW
</td>
</tr>
<tr>
<th>Severity</th>
<td>normal
</td>
</tr>
<tr>
<th>Priority</th>
<td>P
</td>
</tr>
<tr>
<th>Component</th>
<td>LLVM Codegen
</td>
</tr>
<tr>
<th>Assignee</th>
<td>unassignedclangbugs@nondot.org
</td>
</tr>
<tr>
<th>Reporter</th>
<td>cuoq@trust-in-soft.com
</td>
</tr>
<tr>
<th>CC</th>
<td>llvm-bugs@lists.llvm.org
</td>
</tr></table>
<p>
<div>
<pre>Please consider the functions below and the code generated by Clang 6.0.0 and
trunk according to Matt Godbolt's Compiler Explorer
(<a href="https://godbolt.org/g/Z4auAE">https://godbolt.org/g/Z4auAE</a> ):
____
// C code
#include <string.h>
#include <stddef.h>
struct s {
int a;
char t[8];
int b;
} s;
int r1, r2, r3, r4;
void f(char *);
void std_fun(int x, int z) {
char *p = s.t;
r1 = s.a;
r2 = s.b;
memset(p+x, 0, z);
r3 = s.a;
r4 = s.b;
}
void std_fun_twist(int x, int z) {
char *p = (char*)&s + offsetof(struct s, t);
r1 = s.a;
r2 = s.b;
memset(p+x, 0, z);
r3 = s.a;
r4 = s.b;
}
void custom_fun(void) {
struct s s;
char *p = s.t;
r1 = s.a;
r2 = s.b;
f(p);
r3 = s.a;
r4 = s.b;
}
void signed_ptr_offset(int x) {
char *p = s.t;
r1 = s.a;
r2 = s.b;
p[x]=1;
r3 = s.a;
r4 = s.b;
}
void signed_ptr_offset_twist(int x) {
char *p = (char*)&s + offsetof(struct s, t);
r1 = s.a;
r2 = s.b;
p[x]=1;
r3 = s.a;
r4 = s.b;
}
void array(int x) {
r1 = s.a;
r2 = s.b;
s.t[x]=1;
r3 = s.a;
r4 = s.b;
}
_______
Generated Assembly (Clang 6.0.0):
std_fun: # @std_fun
pushq %rbx
movl s(%rip), %ebx
movl %ebx, r1(%rip)
movl s+12(%rip), %eax
movl %eax, r2(%rip)
movslq %edi, %rax
leaq s+4(%rax), %rdi
movslq %esi, %rdx
xorl %esi, %esi
callq memset
movl %ebx, r3(%rip)
movl s+12(%rip), %eax
movl %eax, r4(%rip)
popq %rbx
retq
std_fun_twist: # @std_fun_twist
pushq %rbx
movl s(%rip), %ebx
movl %ebx, r1(%rip)
movl s+12(%rip), %eax
movl %eax, r2(%rip)
movslq %edi, %rax
leaq s+4(%rax), %rdi
movslq %esi, %rdx
xorl %esi, %esi
callq memset
movl %ebx, r3(%rip)
movl s+12(%rip), %eax
movl %eax, r4(%rip)
popq %rbx
retq
custom_fun: # @custom_fun
subq $24, %rsp
leaq 12(%rsp), %rdi
callq f
movl 8(%rsp), %eax
movl 20(%rsp), %ecx
movl %eax, r3(%rip)
movl %ecx, r4(%rip)
addq $24, %rsp
retq
signed_ptr_offset: # @signed_ptr_offset
movl s(%rip), %eax
movl %eax, r1(%rip)
movl s+12(%rip), %ecx
movslq %edi, %rdx
movb $1, s+4(%rdx)
movl %ecx, r2(%rip)
movl %eax, r3(%rip)
movl s+12(%rip), %eax
movl %eax, r4(%rip)
retq
signed_ptr_offset_twist: # @signed_ptr_offset_twist
movl s(%rip), %eax
movl %eax, r1(%rip)
movl s+12(%rip), %ecx
movslq %edi, %rdx
movb $1, s+4(%rdx)
movl %ecx, r2(%rip)
movl %eax, r3(%rip)
movl s+12(%rip), %eax
movl %eax, r4(%rip)
retq
array: # @array
movl s(%rip), %eax
movl %eax, r1(%rip)
movl s+12(%rip), %ecx
movslq %edi, %rdx
movb $1, s+4(%rdx)
movl %ecx, r2(%rip)
movl %eax, r3(%rip)
movl s+12(%rip), %eax
movl %eax, r4(%rip)
retq
___________
The point of interest for each function is whether Clang generates code to
reload the value of the struct members a and b after the struct has been
accessed, the access being made from a pointer initially pointing to member t.
The values are to be stored in variables r3 and r4. A sequence such as the
following indicates that Clang considers that the value of the member a must be
the same after the call to memset as it was before:
callq memset
movl %ebx, r3(%rip)
Note that the variable p is always a pointer to char, and s is always a
variable, which means that the functions cannot be said to be incorrect because
of “strict aliasing” violations. Incidentally, adding "-fno-strict-aliasing" to
the commandline does not change the generated code.
1/ WRONG OPTIMIZATION
In the functions std_fun_twist and signed_ptr_offset_twist, r3 is assigned
without reloading the member a. I do not think that there exist any possible
justification for this optimization in a real compiler used for, for instance,
OS code. Some programmers will use char pointers to access the representation
of structs. The struct's representation is an array of bytes
(<a href="https://port70.net/~nsz/c/c11/n1570.html#6.2.6.1p2">https://port70.net/~nsz/c/c11/n1570.html#6.2.6.1p2</a> ) and these functions are
only doing valid pointer arithmetics within this array.
2/ MISSED OPTIMIZATION
In the function named array, Clang reloads the member b to assign to variable
r4. I think it would be valid to treat any argument x to that function not
between 0 and 7 as causing undefined behavior, and therefore to reuse the value
loaded in %ecx. This is an optimization that GCC already does.
3/ BORDERLINE CASES
While it is difficult to infer intention from absence of optimization, I think
that GCC avoids optimizing the other functions on purpose, making a distinction
between direct array subscripting and use of an intermediate pointer that can
be interpreter as a pointer inside the entire struct. While the C standards do
not distinguish between the two, I think this is a valuable heuristic and I
hope that Clang will align to GCC's behavior in these cases. Regardless of the
decision, each example is either a wrong optimization or a missed optimization,
since Clang always reload the member b and assumes that the member a was not
modified.</pre>
</div>
</p>
<hr>
<span>You are receiving this mail because:</span>
<ul>
<li>You are on the CC list for the bug.</li>
</ul>
</body>
</html>