From: Richard Earnshaw Date: Fri, 20 Dec 2024 18:28:05 +0000 (+0000) Subject: arm: fix incorrect assembly of stm{,ia} as push [PR32363] X-Git-Tag: gdb-16-branchpoint~31 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9edd0af148442f6192529b6edc9017f95ab404d8;p=thirdparty%2Fbinutils-gdb.git arm: fix incorrect assembly of stm{,ia} as push [PR32363] PR/32363. Gas was incorrectly translating stm sp!, {regs} into push {regs} but this is invalid. Conversely, it was also failing to translate stmfd sp!, {lowregs[, lr]} into a 16-bit push instruction. Fortunately stmia SP! is unlikely to be a common idiom on a full-descending stack as it writes values to the stack, then immediately deallocates that bit of the stack. Fixed this and cleaned up the logic somewhat. While there, change some of the ordering so that "ldm base, {base}" is transformed preferentially to LDR. This is in keeping with the general preference in the Arm ARM for avoiding single register LDM instructions. --- diff --git a/gas/config/tc-arm.c b/gas/config/tc-arm.c index 67b431c1591..aee5c8d89ed 100644 --- a/gas/config/tc-arm.c +++ b/gas/config/tc-arm.c @@ -878,6 +878,8 @@ struct asm_opcode #define T_OPCODE_BRANCH 0xe000 +#define T_OPCODE_STMFD 0xe9000000 + #define THUMB_SIZE 2 /* Size of thumb instruction. */ #define THUMB_PP_PC_LR 0x0100 #define THUMB_LOAD_BIT 0x0800 @@ -12352,6 +12354,8 @@ encode_thumb2_multi (bool do_io, int base, unsigned mask, inst.instruction |= base << 16; } +static void do_t_push_pop (void); + static void do_t_ldmstm (void) { @@ -12367,20 +12371,73 @@ do_t_ldmstm (void) unsigned mask; narrow = false; - /* See if we can use a 16-bit instruction. */ - if (inst.instruction < 0xffff /* not ldmdb/stmdb */ - && inst.size_req != 4 - && !(inst.operands[1].imm & ~0xff)) + /* Try to convert ldm/stm to a 16-bit instruction. */ + /* First, handle SP as the base. */ + if (inst.operands[0].reg == REG_SP) + { + /* Try converting to push/pop. */ + if (inst.operands[0].writeback + && (inst.instruction == T_OPCODE_STMFD /* Push. */ + || inst.instruction == T_MNEM_ldmia)) /* Pop. */ + { + inst.instruction = (inst.instruction == T_MNEM_ldmia + ? T_MNEM_pop + : T_MNEM_push); + inst.operands[0] = inst.operands[1]; + do_t_push_pop (); + return; + } + /* For single registers try a simple ldr/str. */ + if (!inst.operands[0].writeback + && (inst.instruction == T_MNEM_stmia + || inst.instruction == T_MNEM_ldmia) + && (inst.operands[1].imm & (inst.operands[1].imm - 1)) == 0) + { + unsigned long opcode = T_MNEM_str_sp; + if (inst.instruction == T_MNEM_ldmia) + opcode = T_MNEM_ldr_sp; + inst.instruction = THUMB_OP16 (opcode); + inst.instruction |= ((ffs (inst.operands[1].imm) - 1) << 8); + narrow = true; + } + } + /* For ldmia/stmia with all low registers we can try 16-bit + encodings. */ + else if (inst.size_req != 4 + && (inst.instruction == T_MNEM_ldmia + || inst.instruction == T_MNEM_stmia) + && inst.operands[0].reg <= 7 + && !(inst.operands[1].imm & ~0xff)) { - mask = 1 << inst.operands[0].reg; + /* For single registers try a simple ldr/str. */ + if (!inst.operands[0].writeback + && (inst.operands[1].imm & (inst.operands[1].imm - 1)) == 0) + { + unsigned long opcode = T_MNEM_ldr; + if (inst.instruction == T_MNEM_stmia) + opcode = T_MNEM_str; - if (inst.operands[0].reg <= 7) + inst.instruction = THUMB_OP16 (opcode); + inst.instruction |= inst.operands[0].reg << 3; + inst.instruction |= (ffs (inst.operands[1].imm) - 1); + narrow = true; + } + /* Finally, try a 16-bit ldm/stm. */ + else { + mask = 1 << inst.operands[0].reg; + + /* STMIA must have writeback; LDMIA must have writeback + if and only if the base register is not in the + transfer list. */ if (inst.instruction == T_MNEM_stmia ? inst.operands[0].writeback : (inst.operands[0].writeback == !(inst.operands[1].imm & mask))) { + /* For STMIA, if the base register is in the + transfer list, the value stored is UNKOWN if it + is not the first register in the list. */ if (inst.instruction == T_MNEM_stmia && (inst.operands[1].imm & mask) && (inst.operands[1].imm & (mask - 1))) @@ -12392,50 +12449,6 @@ do_t_ldmstm (void) inst.instruction |= inst.operands[1].imm; narrow = true; } - else if ((inst.operands[1].imm & (inst.operands[1].imm-1)) == 0) - { - /* This means 1 register in reg list one of 3 situations: - 1. Instruction is stmia, but without writeback. - 2. lmdia without writeback, but with Rn not in - reglist. - 3. ldmia with writeback, but with Rn in reglist. - Case 3 is UNPREDICTABLE behaviour, so we handle - case 1 and 2 which can be converted into a 16-bit - str or ldr. The SP cases are handled below. */ - unsigned long opcode; - /* First, record an error for Case 3. */ - if (inst.operands[1].imm & mask - && inst.operands[0].writeback) - inst.error = - _("having the base register in the register list when " - "using write back is UNPREDICTABLE"); - - opcode = (inst.instruction == T_MNEM_stmia ? T_MNEM_str - : T_MNEM_ldr); - inst.instruction = THUMB_OP16 (opcode); - inst.instruction |= inst.operands[0].reg << 3; - inst.instruction |= (ffs (inst.operands[1].imm)-1); - narrow = true; - } - } - else if (inst.operands[0] .reg == REG_SP) - { - if (inst.operands[0].writeback) - { - inst.instruction = - THUMB_OP16 (inst.instruction == T_MNEM_stmia - ? T_MNEM_push : T_MNEM_pop); - inst.instruction |= inst.operands[1].imm; - narrow = true; - } - else if ((inst.operands[1].imm & (inst.operands[1].imm-1)) == 0) - { - inst.instruction = - THUMB_OP16 (inst.instruction == T_MNEM_stmia - ? T_MNEM_str_sp : T_MNEM_ldr_sp); - inst.instruction |= ((ffs (inst.operands[1].imm)-1) << 8); - narrow = true; - } } } diff --git a/gas/testsuite/gas/arm/thumb2_ldmstm.d b/gas/testsuite/gas/arm/thumb2_ldmstm.d index 295c6a2a129..8662a0490e6 100644 --- a/gas/testsuite/gas/arm/thumb2_ldmstm.d +++ b/gas/testsuite/gas/arm/thumb2_ldmstm.d @@ -15,16 +15,17 @@ Disassembly of section .text: 0[0-9a-f]+ <[^>]+> f856 8c04 ldr.w r8, \[r6, #-4\] 0[0-9a-f]+ <[^>]+> f852 4d04 ldr.w r4, \[r2, #-4\]! 0[0-9a-f]+ <[^>]+> f852 cd04 ldr.w ip, \[r2, #-4\]! -0[0-9a-f]+ <[^>]+> b408 push {r3} +0[0-9a-f]+ <[^>]+> f84d 3b04 str.w r3, \[sp\], #4 0[0-9a-f]+ <[^>]+> f84d 9b04 str.w r9, \[sp\], #4 0[0-9a-f]+ <[^>]+> f8c3 c000 str.w ip, \[r3\] 0[0-9a-f]+ <[^>]+> f844 cb04 str.w ip, \[r4\], #4 -0[0-9a-f]+ <[^>]+> f84d 3d04 str.w r3, \[sp, #-4\]! +0[0-9a-f]+ <[^>]+> b408 push {r3} 0[0-9a-f]+ <[^>]+> f84d 9d04 str.w r9, \[sp, #-4\]! 0[0-9a-f]+ <[^>]+> f847 5c04 str.w r5, \[r7, #-4\] 0[0-9a-f]+ <[^>]+> f846 cc04 str.w ip, \[r6, #-4\] 0[0-9a-f]+ <[^>]+> f846 bd04 str.w fp, \[r6, #-4\]! 0[0-9a-f]+ <[^>]+> f845 8d04 str.w r8, \[r5, #-4\]! +0[0-9a-f]+ <[^>]+> 6800 ldr r0, \[r0, #0\] 0[0-9a-f]+ <[^>]+> c80e ldmia r0!, {r1, r2, r3} 0[0-9a-f]+ <[^>]+> c80f ldmia r0, {r0, r1, r2, r3} 0[0-9a-f]+ <[^>]+> c802 ldmia r0!, {r1} diff --git a/gas/testsuite/gas/arm/thumb2_ldmstm.s b/gas/testsuite/gas/arm/thumb2_ldmstm.s index ab7701c7bab..953e77e4209 100644 --- a/gas/testsuite/gas/arm/thumb2_ldmstm.s +++ b/gas/testsuite/gas/arm/thumb2_ldmstm.s @@ -21,6 +21,7 @@ ldmstm: stmdb r6, {ip} stmdb r6!, {fp} stmdb r5!, {r8} + ldmia r0, {r0} @ Use ldr r0, [r0] @ Valid Thumb-2 encodings of LDM/LDMIA/LDMFD as specified by section @ A8.6.53 of the ARM ARM