Since ARM64 has an ISA with fixed 32-bit instruction width, the move instructions have space for 16-bit unsigned immediate. To move bigger immediate value, we need to move the value in multiple steps using chunks of 16-bits (movz/movk). Due to this, multiple mov instructions are generated to load a single bigger value in register in contrast to x64 where a single mov can load bigger immediate.
I wanted to see if it has been already evaluated or if it is feasible to store such immediate values in a literal pool and load the values from the pool using ldr instead of moving using multi-step movz/movk. I know this would involve accessing memory instead and might end up being slower. In fact, the examples I have tried on clang/gcc, they use movz/movk mostly.
I have also noticed cases where we try to move 2 immediates back to back that are just bytes apart from each other. E.g. Below is the code generated for Vector4.Add(Vector4, Vector4) method where it tries to load the parameters before performing fadd.
D29D9900 movz x0, #0xecc8
F2A785E0 movk x0, #0x3c2f LSL #16
F2C051C0 movk x0, #654 LSL #32
F9400000 ldr x0, [x0] # <==== loads from address 0x6543c2fecc8
FD400410 ldr d16, [x0,#8]
D29D9800 movz x0, #0xecc0
F2A785E0 movk x0, #0x3c2f LSL #16
F2C051C0 movk x0, #654 LSL #32 # <==== loads from address 0x6543c2fecc0
F9400000 ldr x0, [x0]
FD400411 ldr d17, [x0,#8]
0E31D610 fadd v16.2s, v16.2s, v17.2s
The 2nd set of move instructions can be optimized by doing sub x0, x0, 8.
Just to get the stats to see how often we need to generate higher immediate values, I ran crossgen tool on all (or most) of the libraries and dumped the assembly code produced from it for ARM64. Then I extracted out the methods and the portion of code that uses movz/movk pair to load the immediates. There were total 191028 methods crossgened out of which 4578 methods contained pair of movz/movk. In all there were 11856 groups of movz/movk instructions.
You can see the summary in movs.txt.
Some of the questions I am thinking about on seeing the above results:
- Some of the
Utf8Parser methods loads constant multiple times and can it be optimized to reuse the value?
- Is it possible to re-evaluate constant propagation for certain scenarios where the value of constant is big and since it gets propagated, we end up creating these mov sequence multiple times?
- Should we tweak our inline heuristics because of the amount of code generated for ARM64 is more than that for Intel?
- Can we eliminate some of the
movz/movk by doing arithmetic manipulation, the way I mentioned in above example?
- And finally, is it beneficial to use literal pool table for certain scenarios?
References:
category:cq
theme:optimization
skill-level:intermediate
cost:medium
Since ARM64 has an ISA with fixed 32-bit instruction width, the move instructions have space for 16-bit unsigned immediate. To move bigger immediate value, we need to move the value in multiple steps using chunks of 16-bits (
movz/movk). Due to this, multiplemovinstructions are generated to load a single bigger value in register in contrast to x64 where a singlemovcan load bigger immediate.I wanted to see if it has been already evaluated or if it is feasible to store such immediate values in a literal pool and load the values from the pool using
ldrinstead of moving using multi-stepmovz/movk. I know this would involve accessing memory instead and might end up being slower. In fact, the examples I have tried on clang/gcc, they usemovz/movkmostly.I have also noticed cases where we try to move 2 immediates back to back that are just bytes apart from each other. E.g. Below is the code generated for Vector4.Add(Vector4, Vector4) method where it tries to load the parameters before performing
fadd.The 2nd set of move instructions can be optimized by doing
sub x0, x0, 8.Just to get the stats to see how often we need to generate higher immediate values, I ran
crossgentool on all (or most) of the libraries and dumped the assembly code produced from it for ARM64. Then I extracted out the methods and the portion of code that usesmovz/movkpair to load the immediates. There were total 191028 methods crossgened out of which 4578 methods contained pair of movz/movk. In all there were 11856 groups of movz/movk instructions.You can see the summary in movs.txt.
Some of the questions I am thinking about on seeing the above results:
Utf8Parsermethods loads constant multiple times and can it be optimized to reuse the value?movz/movkby doing arithmetic manipulation, the way I mentioned in above example?References:
category:cq
theme:optimization
skill-level:intermediate
cost:medium