llvm / llvm/llvm-project

x86 -O{s,2} --- several small optimization opportunities

Open
#195,397 4 comments 0 reactions 0 assignees View on GitHub
backend:X86 missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Consider the following sample.

```c
#include
#include

int testZero(int num) {
(num - 6) & 0x7
? printf("haha")
: printf("boo");
return 0;
}

int testNum(int num) {
(num - 6) & 0x7
? printf("haha")
: printf("boo");
return num;
}

int testNumSquared(int num) {
(num - 6) & 0x7
? printf("haha")
: printf("boo");
return num * num;
}
```

Compile this sample for x86 with -O2 using Godbolt for trunk. The result is as follows.

```asm
testZero:
push rax
and edi,0x7
cmp edi,0x6
lea rax,[rip+0x0] # e
R_X86_64_PC32 .L.str.1-0x4
lea rdi,[rip+0x0] # 15
R_X86_64_PC32 .L.str-0x4
cmove rdi,rax
xor eax,eax
call 20
R_X86_64_PLT32 printf-0x4
xor eax,eax
pop rcx
ret
data16 data16 cs nop WORD PTR [rax+rax*1+0x0]

testNum:
push rbx
mov ebx,edi
mov eax,edi
and eax,0x7
cmp eax,0x6
lea rax,[rip+0x0] # 42
R_X86_64_PC32 .L.str.1-0x4
lea rdi,[rip+0x0] # 49
R_X86_64_PC32 .L.str-0x4
cmove rdi,rax
xor eax,eax
call 54
R_X86_64_PLT32 printf-0x4
mov eax,ebx
pop rbx
ret
nop DWORD PTR [rax+rax*1+0x0]

testNumSquared:
push rbx
mov ebx,edi
mov eax,edi
and eax,0x7
cmp eax,0x6
lea rax,[rip+0x0] # 72
R_X86_64_PC32 .L.str.1-0x4
lea rdi,[rip+0x0] # 79
R_X86_64_PC32 .L.str-0x4
cmove rdi,rax
xor eax,eax
call 84
R_X86_64_PLT32 printf-0x4
imul ebx,ebx
mov eax,ebx
pop rbx
ret
```

For testZero, observe the following optimization opportunities.

1. Instead of push rax, and later xor eax, eax then pop rcx, the code could have done push 0 then pop rax. Alternatively, it could have delayed push rax until after xor eax, eax right before printf, saving the need for another xor eax, eax later (and then pop rcx becomes pop rax). This is also missed for -Os.
2. The second lea might not need to be rip relative since it could be resolved as a simple lea against the immediately preceding rip relative load once the addresses of the literals are known. The advantage is that the second rip relative load could avoid a 4 byte immediate if the literals are close enough, so the instruction is smaller. This is also missed for -Os.

For testNum, observe the following optimization opportunities in addition to #2 for testZero.

1. Instead of push rbx then pop rbx, the code could have done push rdi then pop rax, avoiding some mov instructions to save edi in ebx and then restoring ebx to eax in the process.
2. It is unnecessary to move edi to eax before doing arithmetic (note how testZero does not do this).

For testNumSquared, observe the following optimization opportunities in addition to #2 for testZero, and #2 for testNum.

1. Could consider doing imul ebx, ebx immediately after mov ebx, edi to hide that latency behind the calls to printf.

Generally speaking, the rationale for these is that fewer instructions are better, and that smaller code is better, per the manual.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.