llvm / llvm/llvm-project

[AArch64] CSR allocation prevents shrink-wrapping of cold call path

Open
#218,610 1 comment 0 reactions 0 assignees View on GitHub
backend:AArch64 llvm:codegen missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

https://godbolt.org/z/3qYzTnEMK

### LLVM

Clang emits the frame record and an x19 save at function entry:

```asm
Perl_pp_gv:
stp x29, x30, [sp, #-32]!
str x19, [sp, #16]
mov x29, sp
adrp x19, PL_stack_sp
...
b.gt .LBB0_2
mov w1, #1
bl Perl_stack_grow
.LBB0_2:
...
str x0, [x19, :lo12:PL_stack_sp]
ldr x19, [sp, #16]
ldp x29, x30, [sp], #32
ret
```

The important part is that PL_stack_sp's address is kept in the callee-saved register x19:

```asm
adrp x19, PL_stack_sp
...
str x0, [x19, :lo12:PL_stack_sp]
```

This creates a CSR use in the entry block. Since LLVM shrink-wrapping runs after register
allocation, the save point must dominate this x19 use, so the prologue cannot be sunk into the
cold block containing the only call.

As a result, the hot path pays for:

```asm
stp x29, x30, [sp, #-32]!
str x19, [sp, #16]
mov x29, sp
...
ldr x19, [sp, #16]
ldp x29, x30, [sp], #32
ret
```
even when Perl_stack_grow is not called.

### GCC output

GCC keeps the hot path frame-free. It uses a caller-saved register for the PL_stack_sp address:

```asm
Perl_pp_gv:
adrp x2, PL_stack_sp
...
ble .L14
...
str x3, [x2, #:lo12:PL_stack_sp]
ret
```
The frame record is sunk into the cold block containing the call:

```asm
.L14:
mov w1, 1
stp x29, x30, [sp, -16]!
mov x29, sp
bl Perl_stack_grow
...
adrp x2, PL_stack_sp
...
ldp x29, x30, [sp], 16
str x3, [x2, #:lo12:PL_stack_sp]
ret
```

So GCC chooses caller-saved registers on the hot path and rematerializes the PL_stack_sp address after the call on the cold path, instead of keeping it live in a CSR across the call.

### Expected behavior

LLVM should ideally generate code closer to GCC here:

- avoid assigning the PL_stack_sp address/base to a callee-saved register when caller-saved registers are available on the hot path;
- prefer rematerializing the global address after the cold call over keeping it live in a CSR across the call;
- allow the hot path to remain frame-free;
- allow shrink-wrapping to sink the frame record and LR save into the cold block containing the only call.

Contributor guide

Open the contributing guide

Research direction

Start with the Godbolt example and compare the LLVM and GCC AArch64 output shown in the issue. Trace LLVM's register allocation and post-allocation shrink-wrapping decisions around the x19 callee-saved register use; done means the hot path avoids the unnecessary frame and the cold call path can receive the sunk frame and LR save.

Written by the indexing model from the issue text.

Assessment

Tech stack
perl
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.