llvm / llvm/llvm-project

[ARM] CostPerUse for high registers should only apply in instructions with 16-bit variants

Open
#168,570 4 comments 0 reactions 0 assignees View on GitHub
backend:ARM
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

ARMRegisterInfo.td defines the integer registers like this:
```
// Integer registers
def R0 : ARMReg< 0, "r0">, DwarfRegNum<[0]>;
def R1 : ARMReg< 1, "r1">, DwarfRegNum<[1]>;
def R2 : ARMReg< 2, "r2">, DwarfRegNum<[2]>;
def R3 : ARMReg< 3, "r3">, DwarfRegNum<[3]>;
def R4 : ARMReg< 4, "r4">, DwarfRegNum<[4]>;
def R5 : ARMReg< 5, "r5">, DwarfRegNum<[5]>;
def R6 : ARMReg< 6, "r6">, DwarfRegNum<[6]>;
def R7 : ARMReg< 7, "r7">, DwarfRegNum<[7]>;
// These require 32-bit instructions.
let CostPerUse = [1] in {
def R8 : ARMReg< 8, "r8">, DwarfRegNum<[8]>;
def R9 : ARMReg< 9, "r9">, DwarfRegNum<[9]>;
def R10 : ARMReg<10, "r10">, DwarfRegNum<[10]>;
def R11 : ARMReg<11, "r11">, DwarfRegNum<[11]>;
def R12 : ARMReg<12, "r12">, DwarfRegNum<[12]>;
let RegAltNameIndices = [RegNamesRaw] in {
def SP : ARMReg<13, "sp", [], ["r13"]>, DwarfRegNum<[13]>;
def LR : ARMReg<14, "lr", [], ["r14"]>, DwarfRegNum<[14]>;
def PC : ARMReg<15, "pc", [], ["r15"]>, DwarfRegNum<[15]>;
}
```
The additional CostPerUse on high registers only makes sense for instructions that have 16-bit variants. The extra CostPerUse can lead to us inserting extra MOV instructions to/from high registers if it results in more instructions that use low registers. An example is the following, reduced from https://github.com/ARM-software/CMSIS-DSP/blob/main/Source/FilteringFunctions/arm_fir_q7.c:
```
#include
void arm_fir_q7_1_16_mve(signed char *pState,
const signed char *pCoeffs,
unsigned int numTaps,
const signed char * __restrict pSrc,
signed char * __restrict pDst,
unsigned int blockSize)
{
int8x16_t vecCoeff = vldrbq_s8(pCoeffs); signed char *pStateCur = &(pState[(numTaps - 1u)]);
const signed char *pTempSrc = pSrc;
const signed char *pSamples = pState;
signed char *pOutput = pDst; signed int blkCnt = blockSize >> 2;
while (blkCnt > 0) {
vstrbq_s32(pStateCur, vldrbq_s32(pTempSrc));
pStateCur += 4;
pTempSrc += 4;
for (int j = 0; j < 4; j++) {
int8x16_t vecIn0 = vld1q(pSamples + j);
signed int acc = vmladavaq(0, vecIn0, vecCoeff);
*pOutput++ = __builtin_arm_ssat((acc >> 7U), 8);
}
pSamples += 4;
blkCnt--;
} signed int residual = blockSize & 3;
for (int i = 0; i < residual; i++)
*pStateCur++ = *pTempSrc++; for (int j = 0; j < residual; j++) {
int8x16_t vecIn0 = vld1q(pSamples + j);
signed int acc = vmladavaq(0, vecIn0, vecCoeff);
*pOutput++ = __builtin_arm_ssat((acc >> 7U), 8);
} pTempSrc = &pState[blockSize];
signed char *pTempDest = pState;
blkCnt = numTaps - 1;
do {
mve_pred16_t p = vctp8q(blkCnt);
vstrbq_p_s8(pTempDest, vldrbq_z_s8(pTempSrc, p), p);
pTempSrc += 16;
pTempDest += 16;
blkCnt -= 16;
}
while (blkCnt > 0);
}
```
When compiled with `clang --target=arm-none-eabi -mcpu=cortex-m55 -O3 -ffast-math -mfloat-abi=hard` the main loop looks like this:
```
.LBB0_2: @ =>This Inner Loop Header: Depth=1
vldrb.u32 q1, [r3], #4
vstrb.32 q1, [r5], #4
mov r9, r5
vldrb.u8 q1, [r4], #4
vmlav.s8 r2, q1, q0
vldrb.u8 q1, [r4, #-3]
vmlav.s8 r10, q1, q0
vldrb.u8 q1, [r4, #-2]
vmlav.s8 r8, q1, q0
vldrb.u8 q1, [r4, #-1]
vmlav.s8 r12, q1, q0
ssat r7, #8, r2, asr #7
ssat r5, #8, r10, asr #7
ssat r2, #8, r8, asr #7
strb r7, [r1], #4
ssat r7, #8, r12, asr #7
strb r5, [r1, #-3]
mov r5, r9
strb r2, [r1, #-2]
strb r7, [r1, #-1]
le lr, .LBB0_2
```
The register allocator has preferred to have a mov from r5 to r9 and back instead of using r9 in an ssat and strb instruction. But these instructions don't have 16-bit variants, so we're getting 32-bit instructions no matter what, so all we've done is pointlessly add two extra mov instructions to the loop.

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.