Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2020-08

AArch64 Register efficiency

6 messages · 2 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

J. Gareth Moreton

Hi everyone.

During my evaluation of the assembly language produced by the AArch64 implementation of the Free Pascal Compiler, I've noticed that it uses the stack an awful lot and, generally, not many of the 28 or so general- purpose registers available for it.

The main problem is that even though x9 to x15 are designed to be used for local variables (caller-saved), the compiler doesn't make use of them because it has to write them to the stack anyway whenever it calls another subroutine, while x0-x7 and x19-x28 are not used because they're considered volatile and may not retain their values after a subroutine call. Nevertheless, leaf functions tend to perform a little better when it comes to register use.

It's gotten me to thinking... for leaf functions (maybe even all functions if research shows it's plausible), after it has been assigned registers (and maybe gone through peephole optimisation), would it be feasible to store a list of used registers with the associated object that the compiler has for said function? That way, when it is called by another routine, the peephole optimizer and the register allocator can see which registers are not used, so it is more likely to use a volatile register rather than the stack to save temporary values. It is also potentially cross-platform.

The only current sticking points I can see for this system are routines that contain blocks of inline assembly language, forcing a line-by-line evaluation of such blocks to see what's in use, and some additional overhead in the register allocator, which might necessitate only enabling the feature under -O3 or -O4 (I'm tempted to say -O4 because of the potential risk of side-effects, even though any such effects are either bugs or due to the programmer going out of their way to trick the compiler with some dangerous assembly language, e.g. literal bytes that represent direct machine code). Thoughts?

Gareth aka. Kit

Reply by email

J. Gareth Moreton

Oh dear, that's a shame. Havoc sounds fun though! Part of me wants to try anyway because I'm masochistic like that, but I'll do that privately.

As an example, here's something from the classes unit - see how many references there are to [sp, #16]. It might not be exactly the same as what you get as I'm working on improving some peephole optimisations, especially the large numbers of "ldr x0,[sp. #16]" instructions:

.section .text.n_classes$_$tbits_$__$$_create$longint$$tbits,"ax" .balign 8
.globl CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS .type CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS,@function CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS:
stp x29,x30,[sp, #-16]!
mov x29,sp
sub sp,sp,#432
str x0,[sp, #16]
str x1,[sp, #8]
str w2,[sp]
ldr x0,[sp, #8]
cmp x0,#1
b.ne .Lj90
ldr x0,[sp, #16]
ldr x1,[sp, #16]
ldr x1,[x1, #104]
blr x1
str x0,[sp, #16]
.Lj90:
ldr x0,[sp, #16]
cbz x0,.Lj87
.Lj92:
add x2,sp,#32
add x1,sp,#56
movz w0,#1
bl fpc_pushexceptaddr
bl fpc_setjmp
ubfiz x0,x0,#0,#32
sxtw x1,w0
str x1,[sp, #224]
cbnz w0,.Lj97
movn x0,#0
str x0,[sp, #24]
ldr x0,[sp, #16]
str wzr,[x0, #16]
ldr x0,[sp, #16]
str wzr,[x0, #20]
ldr x0,[sp, #16]
str xzr,[x0, #8]
ldr x0,[sp, #16]
movn w1,#0
str w1,[x0, #24]
ldr x0,[sp, #16]
movz w1,#1
strb w1,[x0, #28]
ldr w0,[sp]
cmp w0,#0
b.le .Lj100
ldr w1,[sp]
ldr x0,[sp, #16]
bl CLASSES$_$TBITS_$__$$_GROW$LONGINT
.Lj100:
movz x0,#1
str x0,[sp, #24]
ldr x0,[sp, #16]
cmp x0,#0
cset w0,ne
ldr x1,[sp, #8]
cmp x1,#0
cset w1,ne
and w0,w1,w0
cbz w0,.Lj102
ldr x0,[sp, #16]
ldr x1,[sp, #16]
ldr x1,[x1]
ldr x1,[x1, #136]
blr x1
.Lj102:
.Lj97:
bl fpc_popaddrstack
ldr x0,[sp, #224]
cbz x0,.Lj95
add x2,sp,#232
add x1,sp,#256
movz w0,#1
bl fpc_pushexceptaddr
bl fpc_setjmp
ubfiz x0,x0,#0,#32
sxtw x1,w0
str x1,[sp, #424]
cbnz w0,.Lj103
ldr x0,[sp, #8]
cbz x0,.Lj105
ldr x1,[sp, #24]
ldr x0,[sp, #16]
ldr x2,[sp, #16]
ldr x2,[x2]
ldr x2,[x2, #96]
blr x2
.Lj105:
bl fpc_popaddrstack
bl fpc_reraise
.Lj103:
bl fpc_popaddrstack
ldr x0,[sp, #424]
cbz x0,.Lj106
bl fpc_raise_nested
.Lj106:
bl fpc_doneexception
.Lj95:
.Lj87:
ldr x0,[sp, #16]
mov sp,x29
ldp x29,x30,[sp], #16
ret
.Le9:
.size CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS, .Le9 - CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS

Reply by email

Florian Klämpfl
to J. Gareth Moreton: Hi everyone. During my evaluation of the assembly language produced by the AArch64…
J. Gareth Moreton wrote
Hi everyone. During my evaluation of the assembly language produced by the AArch64 implementation of the Free Pascal Compiler, I've noticed that it uses the stack an awful lot and, generally, not many of the 28 or so general- purpose registers available for it.

So it uses memory instead? Can you give an example?

Show quoted text 8 lines
The main problem is that even though x9 to x15 are designed to be used for local variables (caller-saved), the compiler doesn't make use of them because it has to write them to the stack anyway whenever it calls another subroutine, while x0-x7 and x19-x28 are not used because they're considered volatile and may not retain their values after a subroutine call. Nevertheless, leaf functions tend to perform a little better when it comes to register use. It's gotten me to thinking... for leaf functions (maybe even all functions if research shows it's plausible), after it has been assigned registers (and maybe gone through peephole optimisation), would it be feasible to store a list of used registers with the associated object that the compiler has for said function? That way, when it is called by another routine, the peephole optimizer and the register allocator can see which registers are not used, so it is more likely to use a volatile register rather than the stack to save temporary values. It is also potentially cross-platform.

No, we tried this decades ago and it broke horrible havoc.

Reply by email

Florian Klämpfl
to J. Gareth Moreton: Oh dear, that's a shame. Havoc sounds fun though! Part of me wants to try anyway…
J. Gareth Moreton wrote
Oh dear, that's a shame. Havoc sounds fun though! Part of me wants to try anyway because I'm masochistic like that, but I'll do that privately. As an example, here's something from the classes unit - see how many references there are to [sp, #16].

This is not a matter of non-available registers but of the fact that self in a constructor must be always stored in memory else it couldn't be accessed properly in case of an exception. The proper approach to overcome this is imo to do this at the node level:
- find variables which are read a lot but non-regable
- replace read access by a regable tempref which is loaded before the reads

Show quoted text 113 lines
It might not be exactly the same as what you get as I'm working on improving some peephole optimisations, especially the large numbers of "ldr x0,[sp. #16]" instructions: .section .text.n_classes$_$tbits_$__$$_create$longint$$tbits,"ax" .balign 8 .globl CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS .type CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS,@function CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS: stp x29,x30,[sp, #-16]! mov x29,sp sub sp,sp,#432 str x0,[sp, #16] str x1,[sp, #8] str w2,[sp] ldr x0,[sp, #8] cmp x0,#1 b.ne .Lj90 ldr x0,[sp, #16] ldr x1,[sp, #16] ldr x1,[x1, #104] blr x1 str x0,[sp, #16] .Lj90: ldr x0,[sp, #16] cbz x0,.Lj87 .Lj92: add x2,sp,#32 add x1,sp,#56 movz w0,#1 bl fpc_pushexceptaddr bl fpc_setjmp ubfiz x0,x0,#0,#32 sxtw x1,w0 str x1,[sp, #224] cbnz w0,.Lj97 movn x0,#0 str x0,[sp, #24] ldr x0,[sp, #16] str wzr,[x0, #16] ldr x0,[sp, #16] str wzr,[x0, #20] ldr x0,[sp, #16] str xzr,[x0, #8] ldr x0,[sp, #16] movn w1,#0 str w1,[x0, #24] ldr x0,[sp, #16] movz w1,#1 strb w1,[x0, #28] ldr w0,[sp] cmp w0,#0 b.le .Lj100 ldr w1,[sp] ldr x0,[sp, #16] bl CLASSES$_$TBITS_$__$$_GROW$LONGINT .Lj100: movz x0,#1 str x0,[sp, #24] ldr x0,[sp, #16] cmp x0,#0 cset w0,ne ldr x1,[sp, #8] cmp x1,#0 cset w1,ne and w0,w1,w0 cbz w0,.Lj102 ldr x0,[sp, #16] ldr x1,[sp, #16] ldr x1,[x1] ldr x1,[x1, #136] blr x1 .Lj102: .Lj97: bl fpc_popaddrstack ldr x0,[sp, #224] cbz x0,.Lj95 add x2,sp,#232 add x1,sp,#256 movz w0,#1 bl fpc_pushexceptaddr bl fpc_setjmp ubfiz x0,x0,#0,#32 sxtw x1,w0 str x1,[sp, #424] cbnz w0,.Lj103 ldr x0,[sp, #8] cbz x0,.Lj105 ldr x1,[sp, #24] ldr x0,[sp, #16] ldr x2,[sp, #16] ldr x2,[x2] ldr x2,[x2, #96] blr x2 .Lj105: bl fpc_popaddrstack bl fpc_reraise .Lj103: bl fpc_popaddrstack ldr x0,[sp, #424] cbz x0,.Lj106 bl fpc_raise_nested .Lj106: bl fpc_doneexception .Lj95: .Lj87: ldr x0,[sp, #16] mov sp,x29 ldp x29,x30,[sp], #16 ret .Le9: .size CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS, .Le9 - CLASSES$_$TBITS_$__$$_CREATE$LONGINT$$TBITS

Reply by email

J. Gareth Moreton

On Thu 20/08/20 15:36 , Florian Klämpfl via sent:

Show quoted text 22 lines
Am 20.08.20 um 15:09 schrieb J. Gareth Moreton: > Oh dear, that's a shame. Havoc sounds fun though! Part of me wants to try anyway because I'm masochistic like that, but I'll do that privately. > > As an example, here's something from the classes unit - see how many references there are to [sp, #16]. This is not a matter of non-available registers but of the fact that self in a constructor must be always stored in memory else it couldn't be accessed properly in case of an exception. The proper approach to overcome this is imo to do this at the node level: - find variables which are read a lot but non-regable - replace read access by a regable tempref which is loaded before the reads

Ah yes, I can see how that can be a problem. I can only see it being truly robust for leaf functions that don't deal with objects at all, e.g. parts of the RTL, but that won't work properly if all you have to go by are the .ppu files, which probably won't have that information available. I'm still curious if it can be done, but I'll stick with conventional optimisation for now, if I can submit a patch that isn't full of bugs for once!

Gareth aka. Kit

Reply by email