Re: [PATCH] AArch64: Optimize ADD relocations that resolve to zero
Alice Carlotti <[email protected]>
| Newsgroups | gmane.comp.gnu.binutils |
|---|---|
| Message-ID | <[email protected]> |
On Fri, Jul 24, 2026 at 09:58:59AM +0000, Sivan Shani wrote:
> The "ELF for the Arm 64-bit Architecture" specification, section 5.7.9,
> recommends that linkers optimize ADD instructions whose immediate
> becomes zero after relocation. In particular:
>
> ADD x0, x1, 0
> ADD x2, x2, 0
>
> may be replaced with:
>
> MOV x0, x1
> NOP
>
> Apply this optimization to all supported relocations that set an ADD
> immediate field.
I think this optimization is invalid if we're still emitting relocations,
because it breaks idempotency, so we probably need to add a check for that.
I'm not particularly familiar with this generic part of the linker, so I'd
appreciate any clarification from other maintainers on how to check this.
>
> Rewrite additions between different registers as MOV. Rewrite 64-bit
> additions whose source and destination registers are the same as NOP.
> Keep the 32-bit same-register form as MOV because writing to a W
> register clears the upper 32 bits. Do not rewrite instructions using
> SP, since ADD and the ORR encoding underlying MOV interpret register 31
> differently.
>
> Add coverage for every affected relocation and update existing tests.
>
> bfd/
>
> * elfxx-aarch64.c (AARCH64_MOV_REG_OPCODE)
> (AARCH64_NOP_OPCODE): Define.
> (reencode_add_to_mov): Accept the decoded register fields.
> (_bfd_aarch64_elf_put_addend): Optimize ADD relocations whose
> encoded immediate is zero.
>
> ld/testsuite/
>
> * ld-aarch64/aarch64-elf.exp: Run the new ADD-immediate
> optimization tests.
> * ld-aarch64/add-imm-zero-opt.ld: New test linker script.
> * ld-aarch64/add-imm-zero-opt.s: New test.
> * ld-aarch64/add-imm-zero-opt.d: New expected output.
> * ld-aarch64/add-imm-zero-opt-tlsgd.s: New test.
> * ld-aarch64/add-imm-zero-opt-tlsgd.d: New expected output.
> * ld-aarch64/add-imm-zero-opt-tlsld.s: New test.
> * ld-aarch64/add-imm-zero-opt-tlsld.d: New expected output.
> * ld-aarch64/add-imm-zero-opt-tlsdesc.s: New test.
> * ld-aarch64/add-imm-zero-opt-tlsdesc.d: New expected output.
> * ld-aarch64/erratum843419.d: Expect NOP.
> * ld-aarch64/erratum843419_tls_ie.d: Likewise.
> * ld-aarch64/farcall-b-defsym.d: Likewise.
> * ld-aarch64/farcall-b-none-function.d: Likewise.
> * ld-aarch64/farcall-b-section.d: Likewise.
> * ld-aarch64/farcall-b.d: Likewise.
> * ld-aarch64/farcall-back.d: Likewise.
> * ld-aarch64/farcall-bl-defsym.d: Likewise.
> * ld-aarch64/farcall-bl-none-function.d: Likewise.
> * ld-aarch64/farcall-bl-section.d: Likewise.
> * ld-aarch64/farcall-bl.d: Likewise.
> * ld-aarch64/tls-relax-ld-le-small-ilp32.d: Expect MOV and NOP.
> * ld-aarch64/tls-relax-ld-le-small.d: Likewise.
> * ld-aarch64/tls-relax-ld-le-tiny-ilp32.d: Likewise.
> * ld-aarch64/tls-relax-ld-le-tiny.d: Likewise.
> * ld-aarch64/weak-undefined.d: Expect NOP.
> ---
> bfd/elfxx-aarch64.c | 34 ++++++++++++++++---
> ld/testsuite/ld-aarch64/aarch64-elf.exp | 5 +++
> .../ld-aarch64/add-imm-zero-opt-tlsdesc.d | 16 +++++++++
> .../ld-aarch64/add-imm-zero-opt-tlsdesc.s | 16 +++++++++
> .../ld-aarch64/add-imm-zero-opt-tlsgd.d | 15 ++++++++
> .../ld-aarch64/add-imm-zero-opt-tlsgd.s | 16 +++++++++
> .../ld-aarch64/add-imm-zero-opt-tlsld.d | 15 ++++++++
> .../ld-aarch64/add-imm-zero-opt-tlsld.s | 15 ++++++++
> ld/testsuite/ld-aarch64/add-imm-zero-opt.d | 22 ++++++++++++
> ld/testsuite/ld-aarch64/add-imm-zero-opt.ld | 16 +++++++++
> ld/testsuite/ld-aarch64/add-imm-zero-opt.s | 34 +++++++++++++++++++
> ld/testsuite/ld-aarch64/erratum843419.d | 4 +--
> .../ld-aarch64/erratum843419_tls_ie.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-b-defsym.d | 2 +-
> .../ld-aarch64/farcall-b-none-function.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-b-section.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-b.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-back-be.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-back.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-bl-defsym.d | 2 +-
> .../ld-aarch64/farcall-bl-none-function.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-bl-section.d | 2 +-
> ld/testsuite/ld-aarch64/farcall-bl.d | 2 +-
> .../non-contiguous-mem/non-contiguous-ok-2.d | 2 +-
> .../non-contiguous-mem/non-contiguous-ok-4.d | 2 +-
> .../non-contiguous-mem/non-contiguous-ok-5.d | 2 +-
> .../ld-aarch64/tls-relax-ld-le-small-ilp32.d | 4 +--
> .../ld-aarch64/tls-relax-ld-le-small.d | 4 +--
> .../ld-aarch64/tls-relax-ld-le-tiny-ilp32.d | 4 +--
> .../ld-aarch64/tls-relax-ld-le-tiny.d | 4 +--
> .../ld-aarch64/tls-tiny-gd-le-ilp32.d | 2 +-
> ld/testsuite/ld-aarch64/tls-tiny-gd-le.d | 2 +-
> ld/testsuite/ld-aarch64/tlsle-add-hi12-opt.d | 14 ++++++++
> ld/testsuite/ld-aarch64/tlsle-add-hi12-opt.s | 13 +++++++
> ld/testsuite/ld-aarch64/weak-undefined.d | 2 +-
> 35 files changed, 253 insertions(+), 32 deletions(-)
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt-tlsdesc.d
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt-tlsdesc.s
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt-tlsgd.d
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt-tlsgd.s
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt-tlsld.d
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt-tlsld.s
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt.d
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt.ld
> create mode 100644 ld/testsuite/ld-aarch64/add-imm-zero-opt.s
> create mode 100644 ld/testsuite/ld-aarch64/tlsle-add-hi12-opt.d
> create mode 100644 ld/testsuite/ld-aarch64/tlsle-add-hi12-opt.s
>
> diff --git a/bfd/elfxx-aarch64.c b/bfd/elfxx-aarch64.c
> index 69ef148bf9c..ef25d673798 100644
> --- a/bfd/elfxx-aarch64.c
> +++ b/bfd/elfxx-aarch64.c
> @@ -57,6 +57,18 @@ reencode_add_imm (uint32_t insn, uint32_t imm)
> return (insn & ~(MASK (12) << 10)) | ((imm & MASK (12)) << 10);
> }
>
> +/* Base encoding for MOV Wd, Wm (ORR Wd, WZR, Wm). */
> +#define AARCH64_MOV_REG_OPCODE 0x2a0003e0U
> +/* NOP encoding */
> +#define AARCH64_NOP_OPCODE 0xd503201fU
> +
> +/* Reencode ADD immediate as MOV-register (ORR Rd, ZR, Rm). */
> +static inline uint32_t
> +reencode_add_to_mov (uint32_t insn, unsigned int rd, unsigned int rn)
> +{
> + return ((insn & (1U << 31)) | AARCH64_MOV_REG_OPCODE | (rn << 16) | rd);
> +}
> +
> /* Reencode the IMM field of ADR. */
>
> uint32_t
> @@ -261,11 +273,23 @@ _bfd_aarch64_elf_put_addend (bfd *abfd,
> case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12:
> case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12:
> case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
> - /* Corresponds to: add rd, rn, #uimm12 to provide the low order
> - 12 bits of the page offset following
> - BFD_RELOC_AARCH64_ADR_HI21_PCREL which computes the
> - (pc-relative) page base. */
> - contents = reencode_add_imm (contents, addend);
> + /* Optimize an ADD whose relocated 12-bit immediate is zero. */
> + if ((addend & MASK (12)) == 0)
> + {
> + unsigned int rd = contents & MASK (5);
> + unsigned int rn = (contents >> 5) & MASK (5);
> + /* ADD Xd, Xd, #0 --> NOP, check for the 32 bit version
> + since it clear the upper half of the destination register. */
> + if (rd == rn && rd != 31 && (contents & (1U << 31)) != 0)
> + contents = AARCH64_NOP_OPCODE;
> + /* Register 31 denotes SP in ADD but ZR in MOV (ORR). */
> + else if (rd != 31 && rn != 31)
> + contents = reencode_add_to_mov (contents, rd, rn);
> + else
> + contents = reencode_add_imm (contents, addend);
> + }
> + else
> + contents = reencode_add_imm (contents, addend);
> break;
>
> case BFD_RELOC_AARCH64_LD32_GOTPAGE_LO14:
We should confirm that we're actually looking at an expected ADD instruction
before applying this optimization. (I think the relocations already require it
to be the right sort of ADD instruction, but an explicit check is more robust.)
We can also structure the code to avoid repeating the reencode_add_imm call.
How about:
if ((addend & MASK (12)) == 0
&& (contents & 0x7f800000) == 0x11000000
&& (unsigned int rd = (contents & MASK (5) != 31))
&& (unsigned int rn = ((contents >> 5) & MASK (5))) != 31);
{
if (rd == rn && (contents & (1U << 31)) != 0)
contents = AARCH64_NOP_OPCODE;
else
contents = reencode_add_to_mov (contents, rd, rn);
}
else
contents = reencode_add_imm (contents, addend);
break;
Thanks,
Alice