[v1] LoongArch: Fix relaxation alignment with ld -r (PR 33236)

Message ID 20260529074340.943231-1-mengqinggang@loongson.cn
State New
Headers
Series [v1] LoongArch: Fix relaxation alignment with ld -r (PR 33236) |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

mengqinggang May 29, 2026, 7:43 a.m. UTC
  LoongArch has the same issue as RISC-V for PR 33236 [1].

Section alignment can't be adjusted for objects generated by ld -r.
If previous sections are relaxed, the subsequent section maybe misaligned.

To fix this, add an align section and an align relocation before each section
when ld -r. And disabling the default section start address calculation.

ld.lld has fixed this issue in the following two patches [2] [3].

[1] https://sourceware.org/bugzilla/show_bug.cgi?id=33236
[2] https://github.com/llvm/llvm-project/pull/151639
[3] https://github.com/llvm/llvm-project/pull/198147
---
 bfd/elfnn-loongarch.c                         | 152 +++++++++++++++
 bfd/elfxx-loongarch.h                         |  10 +
 ld/emultempl/loongarchelf.em                  | 182 ++++++++++++++++++
 ld/ldlang.c                                   |   8 +-
 .../ld-loongarch-elf/relax-align-ld-r.d       |   9 +
 .../ld-loongarch-elf/relax-align-ld-r.s       |  12 ++
 ld/testsuite/ld-loongarch-elf/relax.exp       |   4 +
 7 files changed, 376 insertions(+), 1 deletion(-)
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
  

Comments

mengqinggang June 5, 2026, 6:41 a.m. UTC | #1
Gentle ping.


在 2026/5/29 15:43, mengqinggang 写道:
> LoongArch has the same issue as RISC-V for PR 33236 [1].
>
> Section alignment can't be adjusted for objects generated by ld -r.
> If previous sections are relaxed, the subsequent section maybe misaligned.
>
> To fix this, add an align section and an align relocation before each section
> when ld -r. And disabling the default section start address calculation.
>
> ld.lld has fixed this issue in the following two patches [2] [3].
>
> [1] https://sourceware.org/bugzilla/show_bug.cgi?id=33236
> [2] https://github.com/llvm/llvm-project/pull/151639
> [3] https://github.com/llvm/llvm-project/pull/198147
> ---
>   bfd/elfnn-loongarch.c                         | 152 +++++++++++++++
>   bfd/elfxx-loongarch.h                         |  10 +
>   ld/emultempl/loongarchelf.em                  | 182 ++++++++++++++++++
>   ld/ldlang.c                                   |   8 +-
>   .../ld-loongarch-elf/relax-align-ld-r.d       |   9 +
>   .../ld-loongarch-elf/relax-align-ld-r.s       |  12 ++
>   ld/testsuite/ld-loongarch-elf/relax.exp       |   4 +
>   7 files changed, 376 insertions(+), 1 deletion(-)
>   create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
>   create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
>
> diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
> index 8f8ec3b0789..ddd321d896f 100644
> --- a/bfd/elfnn-loongarch.c
> +++ b/bfd/elfnn-loongarch.c
> @@ -7067,6 +7067,158 @@ elf_loongarch64_hash_symbol (struct elf_link_hash_entry *h)
>     return _bfd_elf_hash_symbol (h);
>   }
>   
> +/* Write nops to align section.  */
> +
> +static bool
> +loongarch_build_align_nops (asection *section, asection *align_section)
> +{
> +  if (section->output_section == NULL
> +      || align_section->output_section == NULL)
> +    {
> +      _bfd_error_handler (_("Could not assign to an output section."));
> +      return false;
> +    }
> +
> +  bfd_byte *loc = align_section->contents;
> +  for (bfd_size_type i = 0; i < (align_section->size / 4); i++)
> +    {
> +      bfd_putl32 (LARCH_NOP, loc);
> +      loc += 4;
> +    }
> +
> +  return true;
> +}
> +
> +/* Emit an align relocation and a related undefined symbol.  */
> +
> +static bool
> +loongarch_add_align_relocs (asection *sec,
> +			    asection *align_sec,
> +			    bfd* align_bfd)
> +{
> +  elf_backend_data *bed = get_elf_backend_data (align_bfd);
> +
> +  if (elf_section_data (align_sec)->relocs == NULL)
> +    {
> +      align_sec->reloc_count = 1;
> +      align_sec->flags |= SEC_RELOC;
> +
> +      Elf_Internal_Rela *rela = (Elf_Internal_Rela *)
> +	bfd_malloc (sizeof (Elf_Internal_Rela));
> +      if (rela == NULL)
> +	return false;
> +
> +      rela->r_offset = 0;
> +      rela->r_addend = (1 << sec->alignment_power) - 4;
> +      rela->r_info = ELFNN_R_INFO (0, R_LARCH_ALIGN);
> +      elf_section_data (align_sec)->relocs = rela;
> +
> +      Elf_Internal_Shdr *rela_hdr = (Elf_Internal_Shdr *)
> +	bfd_zalloc (align_bfd, sizeof (Elf_Internal_Shdr));
> +      if (rela_hdr == NULL)
> +       return false;
> +
> +      rela_hdr->sh_size = sizeof (Elf_Internal_Rela);
> +      rela_hdr->sh_entsize = sizeof (Elf_Internal_Rela);
> +      rela_hdr->sh_type = SHT_RELA;
> +      rela_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
> +      elf_section_data (align_sec)->rela.hdr = rela_hdr;
> +    }
> +
> +  /* Used in loongarch_elf_relax_section for align relocations.  */
> +  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (align_bfd);
> +  if (symtab_hdr->contents == NULL)
> +    {
> +      Elf_Internal_Sym *sym = (Elf_Internal_Sym *)
> +	bfd_zmalloc (sizeof (Elf_Internal_Sym));
> +      if (sym == NULL)
> +	return false;
> +
> +      /* Match if (r_symndx < symtab_hdr->sh_info) branch in
> +	 loongarch_elf_relocate_section to avoid find link failed.  */
> +      symtab_hdr->sh_info = 1;
> +//      symtab_hdr->sh_size = sizeof (Elf_Internal_Sym);
> +//      symtab_hdr->sh_entsize = sizeof (Elf_Internal_Sym);
> +//      symtab_hdr->sh_type = SHT_SYMTAB;
> +//      symtab_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
> +      symtab_hdr->contents = (unsigned char *) sym;
> +    }
> +
> +  return true;
> +}
> +
> +/* Section name for aligns is the associated section name plus this
> +   string.  */
> +#define ALIGN_SUFFIX ".align"
> +
> +/* Determine and set the size of the align section for a final link.  */
> +
> +bool
> +elfNN_loongarch_size_aligns (bfd *output_bfd,
> +			     bfd *align_bfd,
> +			     struct bfd_link_info *info,
> +			     asection * (*add_align_section)
> +					  (const char *, asection *),
> +			     void (*layout_sections_again) (void))
> +{
> +  bool need_laying_out = false;
> +  for (bfd *input_bfd = info->input_bfds; input_bfd != NULL;
> +       input_bfd = input_bfd->link.next)
> +    {
> +      asection *sec;
> +      asection *align_sec;
> +
> +      if (!is_loongarch_elf (input_bfd)
> +	  || (input_bfd->flags & BFD_LINKER_CREATED) != 0)
> +	continue;
> +
> +      for (sec = input_bfd->sections; sec != NULL; sec = sec->next)
> +	{
> +	  /* If this section is not a code section, do nothing.  */
> +	  if ((sec->flags & SEC_CODE) == 0)
> +	    continue;
> +
> +	  /* If this section is a link-once section that will be
> +	     discarded, then don't create any stubs.  */
> +	  if (sec->output_section == NULL
> +	      || sec->output_section->owner != output_bfd)
> +	    continue;
> +
> +	  if (sec->alignment_power > 2)
> +	    {
> +	      char *s_name;
> +	      size_t namelen;
> +	      bfd_size_type len;
> +	      namelen = strlen (sec->name);
> +	      len = namelen + sizeof (ALIGN_SUFFIX);
> +	      s_name = bfd_alloc (align_bfd, len);
> +	      if (s_name == NULL)
> +		return false;
> +	      memcpy (s_name, sec->name, namelen);
> +	      memcpy (s_name + namelen, ALIGN_SUFFIX, sizeof (ALIGN_SUFFIX));
> +
> +	      align_sec = add_align_section (s_name, sec);
> +
> +	      if (align_sec == NULL)
> +		return false;
> +
> +	      if (! loongarch_build_align_nops (sec, align_sec))
> +		return false;
> +
> +	      if (! loongarch_add_align_relocs (sec, align_sec, align_bfd))
> +		return false;
> +
> +	      need_laying_out = true;
> +	    }
> +	}
> +    }
> +
> +  if (need_laying_out)
> +    layout_sections_again ();
> +
> +  return true;
> +}
> +
>   #define TARGET_LITTLE_SYM loongarch_elfNN_vec
>   #define TARGET_LITTLE_NAME "elfNN-loongarch"
>   #define ELF_ARCH bfd_arch_loongarch
> diff --git a/bfd/elfxx-loongarch.h b/bfd/elfxx-loongarch.h
> index 400b7377ccd..b295b7a9070 100644
> --- a/bfd/elfxx-loongarch.h
> +++ b/bfd/elfxx-loongarch.h
> @@ -45,6 +45,16 @@ bfd_elf64_loongarch_set_data_segment_info (struct bfd_link_info *, int *);
>   bfd_byte *
>   loongarch_write_unsigned_leb128 (bfd_byte *, size_t, bfd_vma) ATTRIBUTE_HIDDEN;
>   
> +extern bool elf64_loongarch_size_aligns
> +  (bfd *, bfd *, struct bfd_link_info *,
> +   struct bfd_section * (*) (const char *, struct bfd_section *),
> +   void (*) (void));
> +
> +extern bool elf32_loongarch_size_aligns
> +  (bfd *, bfd *, struct bfd_link_info *,
> +   struct bfd_section * (*) (const char *, struct bfd_section *),
> +   void (*) (void));
> +
>   /* TRUE if this is a PLT reference to a local IFUNC.  */
>   #define PLT_LOCAL_IFUNC_P(INFO, H) \
>     ((H)->dynindx == -1 \
> diff --git a/ld/emultempl/loongarchelf.em b/ld/emultempl/loongarchelf.em
> index 5f9d4106629..bbb9c378781 100644
> --- a/ld/emultempl/loongarchelf.em
> +++ b/ld/emultempl/loongarchelf.em
> @@ -41,6 +41,10 @@ PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
>   '
>   
>   fragment <<EOF
> +
> +/* Fake input file for align.  */
> +static lang_input_statement_type *align_file;
> +
>   static void
>   larch_elf_before_allocation (void)
>   {
> @@ -61,6 +65,136 @@ larch_elf_before_allocation (void)
>     link_info.relax_pass = 3;
>   }
>   
> +/* Traverse the linker tree to insert the align section
> +   before input section.  */
> +
> +static bool
> +hook_in_align (lang_statement_list_type add,
> +	       asection *input_section,
> +	       lang_statement_union_type **lp)
> +{
> +  bool ret;
> +  lang_statement_union_type *l;
> +
> +  for (; (l = *lp) != NULL; lp = &l->header.next)
> +    {
> +      switch (l->header.type)
> +	{
> +	case lang_constructors_statement_enum:
> +	  ret = hook_in_align (add, input_section, &constructor_list.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_output_section_statement_enum:
> +	  ret = hook_in_align (add, input_section,
> +			       &l->output_section_statement.children.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_wild_statement_enum:
> +	  ret = hook_in_align (add, input_section,
> +			       &l->wild_statement.children.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_group_statement_enum:
> +	  ret = hook_in_align (add, input_section,
> +			       &l->group_statement.children.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_input_section_enum:
> +	  if (l->input_section.section == input_section)
> +	    {
> +	      /* We've found our section.  Insert the align immediately
> +		 before its associated input section.  */
> +	      //*(add.tail) = l->header.next;
> +	      //l->header.next = add.head;
> +	      *lp = add.head;
> +	      add.head->header.next = l;
> +	      return true;
> +	    }
> +	  break;
> +
> +	case lang_data_statement_enum:
> +	case lang_reloc_statement_enum:
> +	case lang_object_symbols_statement_enum:
> +	case lang_output_statement_enum:
> +	case lang_target_statement_enum:
> +	case lang_input_statement_enum:
> +	case lang_assignment_statement_enum:
> +	case lang_padding_statement_enum:
> +	case lang_address_statement_enum:
> +	case lang_fill_statement_enum:
> +	  break;
> +
> +	default:
> +	  FAIL ();
> +	  break;
> +	}
> +    }
> +
> +  return false;
> +}
> +
> +/* Create a new align section, and arrange for it to be linked
> +   immediately before INPUT_SECTION.  */
> +
> +static asection *
> +elf${ELFSIZE}_loongarch_add_align_section (const char *align_sec_name,
> +					   asection *input_section)
> +{
> +  flagword flags;
> +  asection *align_sec;
> +  asection *output_section;
> +  lang_statement_list_type add_child;
> +  lang_output_section_statement_type *os;
> +
> +  flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_CODE
> +	   | SEC_HAS_CONTENTS | SEC_RELOC | SEC_IN_MEMORY | SEC_KEEP);
> +  align_sec = bfd_make_section_anyway_with_flags (align_file->the_bfd,
> +						  align_sec_name, flags);
> +  if (align_sec == NULL)
> +    goto err_ret;
> +
> +  align_sec->veneer = 1;
> +  bfd_set_section_alignment (align_sec, 2);
> +
> +  output_section = input_section->output_section;
> +  os = lang_output_section_get (output_section);
> +
> +  lang_list_init (&add_child);
> +  lang_add_section (&add_child, align_sec, NULL, NULL, os);
> +
> +  if (add_child.head == NULL)
> +    goto err_ret;
> +
> +  align_sec->size = (1 << input_section->alignment_power) - 4;
> +  align_sec->contents = bfd_alloc (align_file->the_bfd, align_sec->size);
> +  if (align_sec->contents == NULL && align_sec->size != 0)
> +    goto err_ret;
> +  align_sec->alloced = 1;
> +
> +  if (hook_in_align (add_child, input_section, &os->children.head))
> +    return align_sec;
> +
> + err_ret:
> +  einfo (_("%X%P: can not make align section: %E\n"));
> +  return NULL;
> +}
> +
> +static void
> +gldloongarch_layout_sections_again (void)
> +{
> +  /* If we have changed sizes of the align sections, then we need
> +     to recalculate all the section offsets.  */
> +  ldelf_map_segments (true);
> +}
> +
>   static void
>   gld${EMULATION_NAME}_after_allocation (void)
>   {
> @@ -78,6 +212,21 @@ gld${EMULATION_NAME}_after_allocation (void)
>   	}
>       }
>   
> +  /* If generating a relocatable output file, we have to add align
> +     at the start of sections.  */
> +  if (align_file != NULL && bfd_link_relocatable (&link_info))
> +    {
> +      if (! elf${ELFSIZE}_loongarch_size_aligns (link_info.output_bfd,
> +			align_file->the_bfd,
> +			&link_info,
> +			&elf${ELFSIZE}_loongarch_add_align_section,
> +			&gldloongarch_layout_sections_again))
> +	{
> +	  einfo (_("%X%P: can not size align section: %E\n"));
> +	  return;
> +	}
> +    }
> +
>     /* The program header size of executable file may increase.  */
>     if (bfd_get_flavour (link_info.output_bfd) == bfd_target_elf_flavour
>         && !bfd_link_relocatable (&link_info))
> @@ -101,7 +250,40 @@ gld${EMULATION_NAME}_after_allocation (void)
>     ldelf_map_segments (need_layout);
>   }
>   
> +/* This is called before the input files are opened.  We create a new
> +   fake input file to hold the align sections.  */
> +
> +static void
> +loongarch_elf_create_output_section_statements (void)
> +{
> +  if (! bfd_link_relocatable (&link_info))
> +    return;
> +
> +  align_file = lang_add_input_file ("linker aligns",
> +				    lang_input_file_is_fake_enum,
> +				    NULL);
> +  align_file->the_bfd = bfd_create ("linker aligns",
> +				    link_info.output_bfd);
> +  if (align_file->the_bfd == NULL
> +      || ! bfd_set_arch_mach (align_file->the_bfd,
> +			      bfd_get_arch (link_info.output_bfd),
> +			      bfd_get_mach (link_info.output_bfd)))
> +    {
> +      fatal (_("%P: can not create BFD: %E\n"));
> +      return;
> +    }
> +
> +  align_file->the_bfd->flags |= BFD_LINKER_CREATED;
> +
> +  Elf_Internal_Ehdr *ehdr = elf_elfheader (align_file->the_bfd);
> +  elf_backend_data *bed = get_elf_backend_data (link_info.output_bfd);
> +  ehdr->e_ident[EI_CLASS] = bed->s->elfclass;
> +
> +  ldlang_add_file (align_file);
> +}
> +
>   EOF
>   
>   LDEMUL_BEFORE_ALLOCATION=larch_elf_before_allocation
>   LDEMUL_AFTER_ALLOCATION=gld${EMULATION_NAME}_after_allocation
> +LDEMUL_CREATE_OUTPUT_SECTION_STATEMENTS=loongarch_elf_create_output_section_statements
> diff --git a/ld/ldlang.c b/ld/ldlang.c
> index 48dd33a49bb..8c3c287a3a0 100644
> --- a/ld/ldlang.c
> +++ b/ld/ldlang.c
> @@ -5769,6 +5769,10 @@ size_input_section
>       {
>         bfd_size_type alignment_needed;
>   
> +      bool synthesize_align = bfd_link_relocatable (&link_info)
> +			      && (i->flags & SEC_CODE) != 0
> +			      && bfd_get_arch (i->owner) == bfd_arch_loongarch;
> +
>         /* Align this section first to the input sections requirement,
>   	 then to the output section's requirement.  If this alignment
>   	 is greater than any seen before, then record it too.  Perform
> @@ -5788,7 +5792,9 @@ size_input_section
>   
>         alignment_needed = align_power (dot, i->alignment_power) - dot;
>   
> -      if (alignment_needed != 0)
> +      /* If synthesized ALIGN may be needed, add one align section
> +	 and one align relocation and disable the default handling.  */
> +      if (alignment_needed != 0 && ! synthesize_align)
>   	{
>   	  insert_pad (this_ptr, fill, TO_SIZE (alignment_needed), o, dot);
>   	  dot += alignment_needed;
> diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
> new file mode 100644
> index 00000000000..a2d19734a59
> --- /dev/null
> +++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
> @@ -0,0 +1,9 @@
> +#as:
> +#ld: -r
> +#objdump: -Dr
> +
> +#...
> +.*R_LARCH_ALIGN.*
> +#...
> +.*R_LARCH_ALIGN.*
> +#...
> diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
> new file mode 100644
> index 00000000000..19555a7148e
> --- /dev/null
> +++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
> @@ -0,0 +1,12 @@
> +# Add an align section before text section when ld -r.
> +# There will be two R_LARCH_ALIGN relocations after ld -r.
> +.text
> +  addi.d $a0, $a0, 1
> +  call36 func
> +  .align 3
> +func:
> +  addi.d $a0, $a0, 1
> +
> +# Only add a align section for code section.
> +.data
> +  .4byte 0x1234
> diff --git a/ld/testsuite/ld-loongarch-elf/relax.exp b/ld/testsuite/ld-loongarch-elf/relax.exp
> index 7c32a65244c..066f1893042 100644
> --- a/ld/testsuite/ld-loongarch-elf/relax.exp
> +++ b/ld/testsuite/ld-loongarch-elf/relax.exp
> @@ -46,6 +46,10 @@ proc run_partial_linking_align_test {} {
>     }
>   }
>   
> +if [istarget loongarch*-*-*] {
> +  run_dump_test "relax-align-ld-r"
> +}
> +
>   if [istarget loongarch64-*-*] {
>     if [isbuild loongarch64-*-*] {
>       run_dump_test "relax-align-ignore-start"
  
Xi Ruoyao June 5, 2026, 9:56 a.m. UTC | #2
On Fri, 2026-05-29 at 15:43 +0800, mengqinggang wrote:
> +      /* Match if (r_symndx < symtab_hdr->sh_info) branch in
> +	 loongarch_elf_relocate_section to avoid find link failed.  */
> +      symtab_hdr->sh_info = 1;
> +//      symtab_hdr->sh_size = sizeof (Elf_Internal_Sym);
> +//      symtab_hdr->sh_entsize = sizeof (Elf_Internal_Sym);
> +//      symtab_hdr->sh_type = SHT_SYMTAB;
> +//      symtab_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;

Hmm, just remove them?

> +      symtab_hdr->contents = (unsigned char *) sym;

I'll test if the change will break Linux kernel module this weekend.
  
Xi Ruoyao June 7, 2026, 9 a.m. UTC | #3
On Fri, 2026-06-05 at 17:56 +0800, Xi Ruoyao via Binutils wrote:
> On Fri, 2026-05-29 at 15:43 +0800, mengqinggang wrote:
> > +      /* Match if (r_symndx < symtab_hdr->sh_info) branch in
> > +	 loongarch_elf_relocate_section to avoid find link failed. 
> > */
> > +      symtab_hdr->sh_info = 1;
> > +//      symtab_hdr->sh_size = sizeof (Elf_Internal_Sym);
> > +//      symtab_hdr->sh_entsize = sizeof (Elf_Internal_Sym);
> > +//      symtab_hdr->sh_type = SHT_SYMTAB;
> > +//      symtab_hdr->sh_addralign = (bfd_vma) 1 << bed->s-
> > >log_file_align;
> 
> Hmm, just remove them?
> 
> > +      symtab_hdr->contents = (unsigned char *) sym;
> 
> I'll test if the change will break Linux kernel module this weekend.

It does not insert the nops when no inputs have R_LARCH_ALIGN, so kernel
module should be ok.

I've not seriously reviewed all the code change though (well I don't
think I'm really capable to do :).
  
mengqinggang June 8, 2026, 6:24 a.m. UTC | #4
Hi Alan, this patch modifies |ld/ldlang.c|. Could you please help review 
it? Thanks!


在 2026/5/29 15:43, mengqinggang 写道:
> LoongArch has the same issue as RISC-V for PR 33236 [1].
>
> Section alignment can't be adjusted for objects generated by ld -r.
> If previous sections are relaxed, the subsequent section maybe misaligned.
>
> To fix this, add an align section and an align relocation before each section
> when ld -r. And disabling the default section start address calculation.
>
> ld.lld has fixed this issue in the following two patches [2] [3].
>
> [1]https://sourceware.org/bugzilla/show_bug.cgi?id=33236
> [2]https://github.com/llvm/llvm-project/pull/151639
> [3]https://github.com/llvm/llvm-project/pull/198147
> ---
>   bfd/elfnn-loongarch.c                         | 152 +++++++++++++++
>   bfd/elfxx-loongarch.h                         |  10 +
>   ld/emultempl/loongarchelf.em                  | 182 ++++++++++++++++++
>   ld/ldlang.c                                   |   8 +-
>   .../ld-loongarch-elf/relax-align-ld-r.d       |   9 +
>   .../ld-loongarch-elf/relax-align-ld-r.s       |  12 ++
>   ld/testsuite/ld-loongarch-elf/relax.exp       |   4 +
>   7 files changed, 376 insertions(+), 1 deletion(-)
>   create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
>   create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
>
> diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
> index 8f8ec3b0789..ddd321d896f 100644
> --- a/bfd/elfnn-loongarch.c
> +++ b/bfd/elfnn-loongarch.c
> @@ -7067,6 +7067,158 @@ elf_loongarch64_hash_symbol (struct elf_link_hash_entry *h)
>     return _bfd_elf_hash_symbol (h);
>   }
>   
> +/* Write nops to align section.  */
> +
> +static bool
> +loongarch_build_align_nops (asection *section, asection *align_section)
> +{
> +  if (section->output_section == NULL
> +      || align_section->output_section == NULL)
> +    {
> +      _bfd_error_handler (_("Could not assign to an output section."));
> +      return false;
> +    }
> +
> +  bfd_byte *loc = align_section->contents;
> +  for (bfd_size_type i = 0; i < (align_section->size / 4); i++)
> +    {
> +      bfd_putl32 (LARCH_NOP, loc);
> +      loc += 4;
> +    }
> +
> +  return true;
> +}
> +
> +/* Emit an align relocation and a related undefined symbol.  */
> +
> +static bool
> +loongarch_add_align_relocs (asection *sec,
> +			    asection *align_sec,
> +			    bfd* align_bfd)
> +{
> +  elf_backend_data *bed = get_elf_backend_data (align_bfd);
> +
> +  if (elf_section_data (align_sec)->relocs == NULL)
> +    {
> +      align_sec->reloc_count = 1;
> +      align_sec->flags |= SEC_RELOC;
> +
> +      Elf_Internal_Rela *rela = (Elf_Internal_Rela *)
> +	bfd_malloc (sizeof (Elf_Internal_Rela));
> +      if (rela == NULL)
> +	return false;
> +
> +      rela->r_offset = 0;
> +      rela->r_addend = (1 << sec->alignment_power) - 4;
> +      rela->r_info = ELFNN_R_INFO (0, R_LARCH_ALIGN);
> +      elf_section_data (align_sec)->relocs = rela;
> +
> +      Elf_Internal_Shdr *rela_hdr = (Elf_Internal_Shdr *)
> +	bfd_zalloc (align_bfd, sizeof (Elf_Internal_Shdr));
> +      if (rela_hdr == NULL)
> +       return false;
> +
> +      rela_hdr->sh_size = sizeof (Elf_Internal_Rela);
> +      rela_hdr->sh_entsize = sizeof (Elf_Internal_Rela);
> +      rela_hdr->sh_type = SHT_RELA;
> +      rela_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
> +      elf_section_data (align_sec)->rela.hdr = rela_hdr;
> +    }
> +
> +  /* Used in loongarch_elf_relax_section for align relocations.  */
> +  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (align_bfd);
> +  if (symtab_hdr->contents == NULL)
> +    {
> +      Elf_Internal_Sym *sym = (Elf_Internal_Sym *)
> +	bfd_zmalloc (sizeof (Elf_Internal_Sym));
> +      if (sym == NULL)
> +	return false;
> +
> +      /* Match if (r_symndx < symtab_hdr->sh_info) branch in
> +	 loongarch_elf_relocate_section to avoid find link failed.  */
> +      symtab_hdr->sh_info = 1;
> +//      symtab_hdr->sh_size = sizeof (Elf_Internal_Sym);
> +//      symtab_hdr->sh_entsize = sizeof (Elf_Internal_Sym);
> +//      symtab_hdr->sh_type = SHT_SYMTAB;
> +//      symtab_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
> +      symtab_hdr->contents = (unsigned char *) sym;
> +    }
> +
> +  return true;
> +}
> +
> +/* Section name for aligns is the associated section name plus this
> +   string.  */
> +#define ALIGN_SUFFIX ".align"
> +
> +/* Determine and set the size of the align section for a final link.  */
> +
> +bool
> +elfNN_loongarch_size_aligns (bfd *output_bfd,
> +			     bfd *align_bfd,
> +			     struct bfd_link_info *info,
> +			     asection * (*add_align_section)
> +					  (const char *, asection *),
> +			     void (*layout_sections_again) (void))
> +{
> +  bool need_laying_out = false;
> +  for (bfd *input_bfd = info->input_bfds; input_bfd != NULL;
> +       input_bfd = input_bfd->link.next)
> +    {
> +      asection *sec;
> +      asection *align_sec;
> +
> +      if (!is_loongarch_elf (input_bfd)
> +	  || (input_bfd->flags & BFD_LINKER_CREATED) != 0)
> +	continue;
> +
> +      for (sec = input_bfd->sections; sec != NULL; sec = sec->next)
> +	{
> +	  /* If this section is not a code section, do nothing.  */
> +	  if ((sec->flags & SEC_CODE) == 0)
> +	    continue;
> +
> +	  /* If this section is a link-once section that will be
> +	     discarded, then don't create any stubs.  */
> +	  if (sec->output_section == NULL
> +	      || sec->output_section->owner != output_bfd)
> +	    continue;
> +
> +	  if (sec->alignment_power > 2)
> +	    {
> +	      char *s_name;
> +	      size_t namelen;
> +	      bfd_size_type len;
> +	      namelen = strlen (sec->name);
> +	      len = namelen + sizeof (ALIGN_SUFFIX);
> +	      s_name = bfd_alloc (align_bfd, len);
> +	      if (s_name == NULL)
> +		return false;
> +	      memcpy (s_name, sec->name, namelen);
> +	      memcpy (s_name + namelen, ALIGN_SUFFIX, sizeof (ALIGN_SUFFIX));
> +
> +	      align_sec = add_align_section (s_name, sec);
> +
> +	      if (align_sec == NULL)
> +		return false;
> +
> +	      if (! loongarch_build_align_nops (sec, align_sec))
> +		return false;
> +
> +	      if (! loongarch_add_align_relocs (sec, align_sec, align_bfd))
> +		return false;
> +
> +	      need_laying_out = true;
> +	    }
> +	}
> +    }
> +
> +  if (need_laying_out)
> +    layout_sections_again ();
> +
> +  return true;
> +}
> +
>   #define TARGET_LITTLE_SYM loongarch_elfNN_vec
>   #define TARGET_LITTLE_NAME "elfNN-loongarch"
>   #define ELF_ARCH bfd_arch_loongarch
> diff --git a/bfd/elfxx-loongarch.h b/bfd/elfxx-loongarch.h
> index 400b7377ccd..b295b7a9070 100644
> --- a/bfd/elfxx-loongarch.h
> +++ b/bfd/elfxx-loongarch.h
> @@ -45,6 +45,16 @@ bfd_elf64_loongarch_set_data_segment_info (struct bfd_link_info *, int *);
>   bfd_byte *
>   loongarch_write_unsigned_leb128 (bfd_byte *, size_t, bfd_vma) ATTRIBUTE_HIDDEN;
>   
> +extern bool elf64_loongarch_size_aligns
> +  (bfd *, bfd *, struct bfd_link_info *,
> +   struct bfd_section * (*) (const char *, struct bfd_section *),
> +   void (*) (void));
> +
> +extern bool elf32_loongarch_size_aligns
> +  (bfd *, bfd *, struct bfd_link_info *,
> +   struct bfd_section * (*) (const char *, struct bfd_section *),
> +   void (*) (void));
> +
>   /* TRUE if this is a PLT reference to a local IFUNC.  */
>   #define PLT_LOCAL_IFUNC_P(INFO, H) \
>     ((H)->dynindx == -1 \
> diff --git a/ld/emultempl/loongarchelf.em b/ld/emultempl/loongarchelf.em
> index 5f9d4106629..bbb9c378781 100644
> --- a/ld/emultempl/loongarchelf.em
> +++ b/ld/emultempl/loongarchelf.em
> @@ -41,6 +41,10 @@ PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
>   '
>   
>   fragment <<EOF + +/* Fake input file for align. */ +static 
> lang_input_statement_type *align_file; + static void 
> larch_elf_before_allocation (void) { @@ -61,6 +65,136 @@ 
> larch_elf_before_allocation (void) link_info.relax_pass = 3; } +/* 
> Traverse the linker tree to insert the align section + before input 
> section. */ + +static bool +hook_in_align (lang_statement_list_type 
> add, + asection *input_section, + lang_statement_union_type **lp) +{ + 
> bool ret; + lang_statement_union_type *l; + + for (; (l = *lp) != 
> NULL; lp = &l->header.next)
> +    {
> +      switch (l->header.type)
> +	{
> +	case lang_constructors_statement_enum:
> +	  ret = hook_in_align (add, input_section, &constructor_list.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_output_section_statement_enum:
> +	  ret = hook_in_align (add, input_section,
> +			       &l->output_section_statement.children.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_wild_statement_enum:
> +	  ret = hook_in_align (add, input_section,
> +			       &l->wild_statement.children.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_group_statement_enum:
> +	  ret = hook_in_align (add, input_section,
> +			       &l->group_statement.children.head);
> +	  if (ret)
> +	    return ret;
> +	  break;
> +
> +	case lang_input_section_enum:
> +	  if (l->input_section.section == input_section)
> +	    {
> +	      /* We've found our section.  Insert the align immediately
> +		 before its associated input section.  */
> +	      //*(add.tail) = l->header.next;
> +	      //l->header.next = add.head;
> +	      *lp = add.head;
> +	      add.head->header.next = l;
> +	      return true;
> +	    }
> +	  break;
> +
> +	case lang_data_statement_enum:
> +	case lang_reloc_statement_enum:
> +	case lang_object_symbols_statement_enum:
> +	case lang_output_statement_enum:
> +	case lang_target_statement_enum:
> +	case lang_input_statement_enum:
> +	case lang_assignment_statement_enum:
> +	case lang_padding_statement_enum:
> +	case lang_address_statement_enum:
> +	case lang_fill_statement_enum:
> +	  break;
> +
> +	default:
> +	  FAIL ();
> +	  break;
> +	}
> +    }
> +
> +  return false;
> +}
> +
> +/* Create a new align section, and arrange for it to be linked
> +   immediately before INPUT_SECTION.  */
> +
> +static asection *
> +elf${ELFSIZE}_loongarch_add_align_section (const char *align_sec_name,
> +					   asection *input_section)
> +{
> +  flagword flags;
> +  asection *align_sec;
> +  asection *output_section;
> +  lang_statement_list_type add_child;
> +  lang_output_section_statement_type *os;
> +
> +  flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_CODE
> +	   | SEC_HAS_CONTENTS | SEC_RELOC | SEC_IN_MEMORY | SEC_KEEP);
> +  align_sec = bfd_make_section_anyway_with_flags (align_file->the_bfd,
> +						  align_sec_name, flags);
> +  if (align_sec == NULL)
> +    goto err_ret;
> +
> +  align_sec->veneer = 1;
> +  bfd_set_section_alignment (align_sec, 2);
> +
> +  output_section = input_section->output_section;
> +  os = lang_output_section_get (output_section);
> +
> +  lang_list_init (&add_child);
> +  lang_add_section (&add_child, align_sec, NULL, NULL, os);
> +
> +  if (add_child.head == NULL)
> +    goto err_ret;
> +
> +  align_sec->size = (1 << input_section->alignment_power) - 4;
> +  align_sec->contents = bfd_alloc (align_file->the_bfd, align_sec->size);
> +  if (align_sec->contents == NULL && align_sec->size != 0)
> +    goto err_ret;
> +  align_sec->alloced = 1;
> +
> +  if (hook_in_align (add_child, input_section, &os->children.head))
> +    return align_sec;
> +
> + err_ret:
> +  einfo (_("%X%P: can not make align section: %E\n"));
> +  return NULL;
> +}
> +
> +static void
> +gldloongarch_layout_sections_again (void)
> +{
> +  /* If we have changed sizes of the align sections, then we need
> +     to recalculate all the section offsets.  */
> +  ldelf_map_segments (true);
> +}
> +
>   static void
>   gld${EMULATION_NAME}_after_allocation (void)
>   {
> @@ -78,6 +212,21 @@ gld${EMULATION_NAME}_after_allocation (void)
>   	}
>       }
>   
> +  /* If generating a relocatable output file, we have to add align
> +     at the start of sections.  */
> +  if (align_file != NULL && bfd_link_relocatable (&link_info))
> +    {
> +      if (! elf${ELFSIZE}_loongarch_size_aligns (link_info.output_bfd,
> +			align_file->the_bfd,
> +			&link_info,
> +			&elf${ELFSIZE}_loongarch_add_align_section,
> +			&gldloongarch_layout_sections_again))
> +	{
> +	  einfo (_("%X%P: can not size align section: %E\n")); + return; + } + } + /* The program header size of executable file 
> may increase. */ if (bfd_get_flavour (link_info.output_bfd) == 
> bfd_target_elf_flavour && !bfd_link_relocatable (&link_info)) @@ 
> -101,7 +250,40 @@ gld${EMULATION_NAME}_after_allocation (void) 
> ldelf_map_segments (need_layout); } +/* This is called before the 
> input files are opened. We create a new + fake input file to hold the 
> align sections. */ + +static void 
> +loongarch_elf_create_output_section_statements (void) +{ + if (! 
> bfd_link_relocatable (&link_info)) + return; + + align_file = 
> lang_add_input_file ("linker aligns",
> +				    lang_input_file_is_fake_enum,
> +				    NULL);
> +  align_file->the_bfd = bfd_create ("linker aligns",
> +				    link_info.output_bfd);
> +  if (align_file->the_bfd == NULL
> +      || ! bfd_set_arch_mach (align_file->the_bfd,
> +			      bfd_get_arch (link_info.output_bfd),
> +			      bfd_get_mach (link_info.output_bfd)))
> +    {
> +      fatal (_("%P: can not create BFD: %E\n"));
> +      return;
> +    }
> +
> +  align_file->the_bfd->flags |= BFD_LINKER_CREATED;
> +
> +  Elf_Internal_Ehdr *ehdr = elf_elfheader (align_file->the_bfd);
> +  elf_backend_data *bed = get_elf_backend_data (link_info.output_bfd);
> +  ehdr->e_ident[EI_CLASS] = bed->s->elfclass;
> +
> +  ldlang_add_file (align_file);
> +}
> +
>   EOF
>   
>   LDEMUL_BEFORE_ALLOCATION=larch_elf_before_allocation
>   LDEMUL_AFTER_ALLOCATION=gld${EMULATION_NAME}_after_allocation
> +LDEMUL_CREATE_OUTPUT_SECTION_STATEMENTS=loongarch_elf_create_output_section_statements
> diff --git a/ld/ldlang.c b/ld/ldlang.c
> index 48dd33a49bb..8c3c287a3a0 100644
> --- a/ld/ldlang.c
> +++ b/ld/ldlang.c
> @@ -5769,6 +5769,10 @@ size_input_section
>       {
>         bfd_size_type alignment_needed;
>   
> +      bool synthesize_align = bfd_link_relocatable (&link_info)
> +			      && (i->flags & SEC_CODE) != 0
> +			      && bfd_get_arch (i->owner) == bfd_arch_loongarch;
> +
>         /* Align this section first to the input sections requirement,
>   	 then to the output section's requirement.  If this alignment
>   	 is greater than any seen before, then record it too.  Perform
> @@ -5788,7 +5792,9 @@ size_input_section
>   
>         alignment_needed = align_power (dot, i->alignment_power) - dot;
>   
> -      if (alignment_needed != 0)
> +      /* If synthesized ALIGN may be needed, add one align section
> +	 and one align relocation and disable the default handling.  */
> +      if (alignment_needed != 0 && ! synthesize_align)
>   	{
>   	  insert_pad (this_ptr, fill, TO_SIZE (alignment_needed), o, dot);
>   	  dot += alignment_needed;
> diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
> new file mode 100644
> index 00000000000..a2d19734a59
> --- /dev/null
> +++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
> @@ -0,0 +1,9 @@
> +#as:
> +#ld: -r
> +#objdump: -Dr
> +
> +#...
> +.*R_LARCH_ALIGN.*
> +#...
> +.*R_LARCH_ALIGN.*
> +#...
> diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
> new file mode 100644
> index 00000000000..19555a7148e
> --- /dev/null
> +++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
> @@ -0,0 +1,12 @@
> +# Add an align section before text section when ld -r.
> +# There will be two R_LARCH_ALIGN relocations after ld -r.
> +.text
> +  addi.d $a0, $a0, 1
> +  call36 func
> +  .align 3
> +func:
> +  addi.d $a0, $a0, 1
> +
> +# Only add a align section for code section.
> +.data
> +  .4byte 0x1234
> diff --git a/ld/testsuite/ld-loongarch-elf/relax.exp b/ld/testsuite/ld-loongarch-elf/relax.exp
> index 7c32a65244c..066f1893042 100644
> --- a/ld/testsuite/ld-loongarch-elf/relax.exp
> +++ b/ld/testsuite/ld-loongarch-elf/relax.exp
> @@ -46,6 +46,10 @@ proc run_partial_linking_align_test {} {
>     }
>   }
>   
> +if [istarget loongarch*-*-*] {
> +  run_dump_test "relax-align-ld-r"
> +}
> +
>   if [istarget loongarch64-*-*] {
>     if [isbuild loongarch64-*-*] {
>       run_dump_test "relax-align-ignore-start"
  
Alan Modra June 10, 2026, 10:50 p.m. UTC | #5
On Mon, Jun 08, 2026 at 02:24:01PM +0800, mengqinggang wrote:
> > diff --git a/ld/ldlang.c b/ld/ldlang.c
> > index 48dd33a49bb..8c3c287a3a0 100644
> > --- a/ld/ldlang.c
> > +++ b/ld/ldlang.c
> > @@ -5769,6 +5769,10 @@ size_input_section
> >       {
> >         bfd_size_type alignment_needed;
> > +      bool synthesize_align = bfd_link_relocatable (&link_info)
> > +			      && (i->flags & SEC_CODE) != 0
> > +			      && bfd_get_arch (i->owner) == bfd_arch_loongarch;
> > +
> >         /* Align this section first to the input sections requirement,
> >   	 then to the output section's requirement.  If this alignment
> >   	 is greater than any seen before, then record it too.  Perform
> > @@ -5788,7 +5792,9 @@ size_input_section
> >         alignment_needed = align_power (dot, i->alignment_power) - dot;
> > -      if (alignment_needed != 0)
> > +      /* If synthesized ALIGN may be needed, add one align section
> > +	 and one align relocation and disable the default handling.  */
> > +      if (alignment_needed != 0 && ! synthesize_align)
> >   	{
> >   	  insert_pad (this_ptr, fill, TO_SIZE (alignment_needed), o, dot);
> >   	  dot += alignment_needed;

Architecture specific code isn't supposed to be in ldlang.c.  Yes, I
know there is a bfd_arch_tic54x test in there.  I don't like that one
either.

I haven't looked too deeply into what your patch does, but I think you
should be able to stop ldlang.c:size_input_section padding by changing
the input section alignment_power in add_align_section (and tweak
loongarch_add_align_relocs to not use the sec->alignment_power but
instead align_sec->size).
  
mengqinggang June 11, 2026, 1:55 a.m. UTC | #6
Thanks for your feedback. I will make changes based on your advice.


在 2026/6/11 06:50, Alan Modra 写道:
> On Mon, Jun 08, 2026 at 02:24:01PM +0800, mengqinggang wrote:
>>> diff --git a/ld/ldlang.c b/ld/ldlang.c
>>> index 48dd33a49bb..8c3c287a3a0 100644
>>> --- a/ld/ldlang.c
>>> +++ b/ld/ldlang.c
>>> @@ -5769,6 +5769,10 @@ size_input_section
>>>        {
>>>          bfd_size_type alignment_needed;
>>> +      bool synthesize_align = bfd_link_relocatable (&link_info)
>>> +			      && (i->flags & SEC_CODE) != 0
>>> +			      && bfd_get_arch (i->owner) == bfd_arch_loongarch;
>>> +
>>>          /* Align this section first to the input sections requirement,
>>>    	 then to the output section's requirement.  If this alignment
>>>    	 is greater than any seen before, then record it too.  Perform
>>> @@ -5788,7 +5792,9 @@ size_input_section
>>>          alignment_needed = align_power (dot, i->alignment_power) - dot;
>>> -      if (alignment_needed != 0)
>>> +      /* If synthesized ALIGN may be needed, add one align section
>>> +	 and one align relocation and disable the default handling.  */
>>> +      if (alignment_needed != 0 && ! synthesize_align)
>>>    	{
>>>    	  insert_pad (this_ptr, fill, TO_SIZE (alignment_needed), o, dot);
>>>    	  dot += alignment_needed;
> Architecture specific code isn't supposed to be in ldlang.c.  Yes, I
> know there is a bfd_arch_tic54x test in there.  I don't like that one
> either.
>
> I haven't looked too deeply into what your patch does, but I think you
> should be able to stop ldlang.c:size_input_section padding by changing
> the input section alignment_power in add_align_section (and tweak
> loongarch_add_align_relocs to not use the sec->alignment_power but
> instead align_sec->size).
>
  

Patch

diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index 8f8ec3b0789..ddd321d896f 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -7067,6 +7067,158 @@  elf_loongarch64_hash_symbol (struct elf_link_hash_entry *h)
   return _bfd_elf_hash_symbol (h);
 }
 
+/* Write nops to align section.  */
+
+static bool
+loongarch_build_align_nops (asection *section, asection *align_section)
+{
+  if (section->output_section == NULL
+      || align_section->output_section == NULL)
+    {
+      _bfd_error_handler (_("Could not assign to an output section."));
+      return false;
+    }
+
+  bfd_byte *loc = align_section->contents;
+  for (bfd_size_type i = 0; i < (align_section->size / 4); i++)
+    {
+      bfd_putl32 (LARCH_NOP, loc);
+      loc += 4;
+    }
+
+  return true;
+}
+
+/* Emit an align relocation and a related undefined symbol.  */
+
+static bool
+loongarch_add_align_relocs (asection *sec,
+			    asection *align_sec,
+			    bfd* align_bfd)
+{
+  elf_backend_data *bed = get_elf_backend_data (align_bfd);
+
+  if (elf_section_data (align_sec)->relocs == NULL)
+    {
+      align_sec->reloc_count = 1;
+      align_sec->flags |= SEC_RELOC;
+
+      Elf_Internal_Rela *rela = (Elf_Internal_Rela *)
+	bfd_malloc (sizeof (Elf_Internal_Rela));
+      if (rela == NULL)
+	return false;
+
+      rela->r_offset = 0;
+      rela->r_addend = (1 << sec->alignment_power) - 4;
+      rela->r_info = ELFNN_R_INFO (0, R_LARCH_ALIGN);
+      elf_section_data (align_sec)->relocs = rela;
+
+      Elf_Internal_Shdr *rela_hdr = (Elf_Internal_Shdr *)
+	bfd_zalloc (align_bfd, sizeof (Elf_Internal_Shdr));
+      if (rela_hdr == NULL)
+       return false;
+
+      rela_hdr->sh_size = sizeof (Elf_Internal_Rela);
+      rela_hdr->sh_entsize = sizeof (Elf_Internal_Rela);
+      rela_hdr->sh_type = SHT_RELA;
+      rela_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
+      elf_section_data (align_sec)->rela.hdr = rela_hdr;
+    }
+
+  /* Used in loongarch_elf_relax_section for align relocations.  */
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (align_bfd);
+  if (symtab_hdr->contents == NULL)
+    {
+      Elf_Internal_Sym *sym = (Elf_Internal_Sym *)
+	bfd_zmalloc (sizeof (Elf_Internal_Sym));
+      if (sym == NULL)
+	return false;
+
+      /* Match if (r_symndx < symtab_hdr->sh_info) branch in
+	 loongarch_elf_relocate_section to avoid find link failed.  */
+      symtab_hdr->sh_info = 1;
+//      symtab_hdr->sh_size = sizeof (Elf_Internal_Sym);
+//      symtab_hdr->sh_entsize = sizeof (Elf_Internal_Sym);
+//      symtab_hdr->sh_type = SHT_SYMTAB;
+//      symtab_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
+      symtab_hdr->contents = (unsigned char *) sym;
+    }
+
+  return true;
+}
+
+/* Section name for aligns is the associated section name plus this
+   string.  */
+#define ALIGN_SUFFIX ".align"
+
+/* Determine and set the size of the align section for a final link.  */
+
+bool
+elfNN_loongarch_size_aligns (bfd *output_bfd,
+			     bfd *align_bfd,
+			     struct bfd_link_info *info,
+			     asection * (*add_align_section)
+					  (const char *, asection *),
+			     void (*layout_sections_again) (void))
+{
+  bool need_laying_out = false;
+  for (bfd *input_bfd = info->input_bfds; input_bfd != NULL;
+       input_bfd = input_bfd->link.next)
+    {
+      asection *sec;
+      asection *align_sec;
+
+      if (!is_loongarch_elf (input_bfd)
+	  || (input_bfd->flags & BFD_LINKER_CREATED) != 0)
+	continue;
+
+      for (sec = input_bfd->sections; sec != NULL; sec = sec->next)
+	{
+	  /* If this section is not a code section, do nothing.  */
+	  if ((sec->flags & SEC_CODE) == 0)
+	    continue;
+
+	  /* If this section is a link-once section that will be
+	     discarded, then don't create any stubs.  */
+	  if (sec->output_section == NULL
+	      || sec->output_section->owner != output_bfd)
+	    continue;
+
+	  if (sec->alignment_power > 2)
+	    {
+	      char *s_name;
+	      size_t namelen;
+	      bfd_size_type len;
+	      namelen = strlen (sec->name);
+	      len = namelen + sizeof (ALIGN_SUFFIX);
+	      s_name = bfd_alloc (align_bfd, len);
+	      if (s_name == NULL)
+		return false;
+	      memcpy (s_name, sec->name, namelen);
+	      memcpy (s_name + namelen, ALIGN_SUFFIX, sizeof (ALIGN_SUFFIX));
+
+	      align_sec = add_align_section (s_name, sec);
+
+	      if (align_sec == NULL)
+		return false;
+
+	      if (! loongarch_build_align_nops (sec, align_sec))
+		return false;
+
+	      if (! loongarch_add_align_relocs (sec, align_sec, align_bfd))
+		return false;
+
+	      need_laying_out = true;
+	    }
+	}
+    }
+
+  if (need_laying_out)
+    layout_sections_again ();
+
+  return true;
+}
+
 #define TARGET_LITTLE_SYM loongarch_elfNN_vec
 #define TARGET_LITTLE_NAME "elfNN-loongarch"
 #define ELF_ARCH bfd_arch_loongarch
diff --git a/bfd/elfxx-loongarch.h b/bfd/elfxx-loongarch.h
index 400b7377ccd..b295b7a9070 100644
--- a/bfd/elfxx-loongarch.h
+++ b/bfd/elfxx-loongarch.h
@@ -45,6 +45,16 @@  bfd_elf64_loongarch_set_data_segment_info (struct bfd_link_info *, int *);
 bfd_byte *
 loongarch_write_unsigned_leb128 (bfd_byte *, size_t, bfd_vma) ATTRIBUTE_HIDDEN;
 
+extern bool elf64_loongarch_size_aligns
+  (bfd *, bfd *, struct bfd_link_info *,
+   struct bfd_section * (*) (const char *, struct bfd_section *),
+   void (*) (void));
+
+extern bool elf32_loongarch_size_aligns
+  (bfd *, bfd *, struct bfd_link_info *,
+   struct bfd_section * (*) (const char *, struct bfd_section *),
+   void (*) (void));
+
 /* TRUE if this is a PLT reference to a local IFUNC.  */
 #define PLT_LOCAL_IFUNC_P(INFO, H) \
   ((H)->dynindx == -1 \
diff --git a/ld/emultempl/loongarchelf.em b/ld/emultempl/loongarchelf.em
index 5f9d4106629..bbb9c378781 100644
--- a/ld/emultempl/loongarchelf.em
+++ b/ld/emultempl/loongarchelf.em
@@ -41,6 +41,10 @@  PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
 '
 
 fragment <<EOF
+
+/* Fake input file for align.  */
+static lang_input_statement_type *align_file;
+
 static void
 larch_elf_before_allocation (void)
 {
@@ -61,6 +65,136 @@  larch_elf_before_allocation (void)
   link_info.relax_pass = 3;
 }
 
+/* Traverse the linker tree to insert the align section
+   before input section.  */
+
+static bool
+hook_in_align (lang_statement_list_type add,
+	       asection *input_section,
+	       lang_statement_union_type **lp)
+{
+  bool ret;
+  lang_statement_union_type *l;
+
+  for (; (l = *lp) != NULL; lp = &l->header.next)
+    {
+      switch (l->header.type)
+	{
+	case lang_constructors_statement_enum:
+	  ret = hook_in_align (add, input_section, &constructor_list.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_output_section_statement_enum:
+	  ret = hook_in_align (add, input_section,
+			       &l->output_section_statement.children.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_wild_statement_enum:
+	  ret = hook_in_align (add, input_section,
+			       &l->wild_statement.children.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_group_statement_enum:
+	  ret = hook_in_align (add, input_section,
+			       &l->group_statement.children.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_input_section_enum:
+	  if (l->input_section.section == input_section)
+	    {
+	      /* We've found our section.  Insert the align immediately
+		 before its associated input section.  */
+	      //*(add.tail) = l->header.next;
+	      //l->header.next = add.head;
+	      *lp = add.head;
+	      add.head->header.next = l;
+	      return true;
+	    }
+	  break;
+
+	case lang_data_statement_enum:
+	case lang_reloc_statement_enum:
+	case lang_object_symbols_statement_enum:
+	case lang_output_statement_enum:
+	case lang_target_statement_enum:
+	case lang_input_statement_enum:
+	case lang_assignment_statement_enum:
+	case lang_padding_statement_enum:
+	case lang_address_statement_enum:
+	case lang_fill_statement_enum:
+	  break;
+
+	default:
+	  FAIL ();
+	  break;
+	}
+    }
+
+  return false;
+}
+
+/* Create a new align section, and arrange for it to be linked
+   immediately before INPUT_SECTION.  */
+
+static asection *
+elf${ELFSIZE}_loongarch_add_align_section (const char *align_sec_name,
+					   asection *input_section)
+{
+  flagword flags;
+  asection *align_sec;
+  asection *output_section;
+  lang_statement_list_type add_child;
+  lang_output_section_statement_type *os;
+
+  flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_CODE
+	   | SEC_HAS_CONTENTS | SEC_RELOC | SEC_IN_MEMORY | SEC_KEEP);
+  align_sec = bfd_make_section_anyway_with_flags (align_file->the_bfd,
+						  align_sec_name, flags);
+  if (align_sec == NULL)
+    goto err_ret;
+
+  align_sec->veneer = 1;
+  bfd_set_section_alignment (align_sec, 2);
+
+  output_section = input_section->output_section;
+  os = lang_output_section_get (output_section);
+
+  lang_list_init (&add_child);
+  lang_add_section (&add_child, align_sec, NULL, NULL, os);
+
+  if (add_child.head == NULL)
+    goto err_ret;
+
+  align_sec->size = (1 << input_section->alignment_power) - 4;
+  align_sec->contents = bfd_alloc (align_file->the_bfd, align_sec->size);
+  if (align_sec->contents == NULL && align_sec->size != 0)
+    goto err_ret;
+  align_sec->alloced = 1;
+
+  if (hook_in_align (add_child, input_section, &os->children.head))
+    return align_sec;
+
+ err_ret:
+  einfo (_("%X%P: can not make align section: %E\n"));
+  return NULL;
+}
+
+static void
+gldloongarch_layout_sections_again (void)
+{
+  /* If we have changed sizes of the align sections, then we need
+     to recalculate all the section offsets.  */
+  ldelf_map_segments (true);
+}
+
 static void
 gld${EMULATION_NAME}_after_allocation (void)
 {
@@ -78,6 +212,21 @@  gld${EMULATION_NAME}_after_allocation (void)
 	}
     }
 
+  /* If generating a relocatable output file, we have to add align
+     at the start of sections.  */
+  if (align_file != NULL && bfd_link_relocatable (&link_info))
+    {
+      if (! elf${ELFSIZE}_loongarch_size_aligns (link_info.output_bfd,
+			align_file->the_bfd,
+			&link_info,
+			&elf${ELFSIZE}_loongarch_add_align_section,
+			&gldloongarch_layout_sections_again))
+	{
+	  einfo (_("%X%P: can not size align section: %E\n"));
+	  return;
+	}
+    }
+
   /* The program header size of executable file may increase.  */
   if (bfd_get_flavour (link_info.output_bfd) == bfd_target_elf_flavour
       && !bfd_link_relocatable (&link_info))
@@ -101,7 +250,40 @@  gld${EMULATION_NAME}_after_allocation (void)
   ldelf_map_segments (need_layout);
 }
 
+/* This is called before the input files are opened.  We create a new
+   fake input file to hold the align sections.  */
+
+static void
+loongarch_elf_create_output_section_statements (void)
+{
+  if (! bfd_link_relocatable (&link_info))
+    return;
+
+  align_file = lang_add_input_file ("linker aligns",
+				    lang_input_file_is_fake_enum,
+				    NULL);
+  align_file->the_bfd = bfd_create ("linker aligns",
+				    link_info.output_bfd);
+  if (align_file->the_bfd == NULL
+      || ! bfd_set_arch_mach (align_file->the_bfd,
+			      bfd_get_arch (link_info.output_bfd),
+			      bfd_get_mach (link_info.output_bfd)))
+    {
+      fatal (_("%P: can not create BFD: %E\n"));
+      return;
+    }
+
+  align_file->the_bfd->flags |= BFD_LINKER_CREATED;
+
+  Elf_Internal_Ehdr *ehdr = elf_elfheader (align_file->the_bfd);
+  elf_backend_data *bed = get_elf_backend_data (link_info.output_bfd);
+  ehdr->e_ident[EI_CLASS] = bed->s->elfclass;
+
+  ldlang_add_file (align_file);
+}
+
 EOF
 
 LDEMUL_BEFORE_ALLOCATION=larch_elf_before_allocation
 LDEMUL_AFTER_ALLOCATION=gld${EMULATION_NAME}_after_allocation
+LDEMUL_CREATE_OUTPUT_SECTION_STATEMENTS=loongarch_elf_create_output_section_statements
diff --git a/ld/ldlang.c b/ld/ldlang.c
index 48dd33a49bb..8c3c287a3a0 100644
--- a/ld/ldlang.c
+++ b/ld/ldlang.c
@@ -5769,6 +5769,10 @@  size_input_section
     {
       bfd_size_type alignment_needed;
 
+      bool synthesize_align = bfd_link_relocatable (&link_info)
+			      && (i->flags & SEC_CODE) != 0
+			      && bfd_get_arch (i->owner) == bfd_arch_loongarch;
+
       /* Align this section first to the input sections requirement,
 	 then to the output section's requirement.  If this alignment
 	 is greater than any seen before, then record it too.  Perform
@@ -5788,7 +5792,9 @@  size_input_section
 
       alignment_needed = align_power (dot, i->alignment_power) - dot;
 
-      if (alignment_needed != 0)
+      /* If synthesized ALIGN may be needed, add one align section
+	 and one align relocation and disable the default handling.  */
+      if (alignment_needed != 0 && ! synthesize_align)
 	{
 	  insert_pad (this_ptr, fill, TO_SIZE (alignment_needed), o, dot);
 	  dot += alignment_needed;
diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
new file mode 100644
index 00000000000..a2d19734a59
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
@@ -0,0 +1,9 @@ 
+#as:
+#ld: -r
+#objdump: -Dr
+
+#...
+.*R_LARCH_ALIGN.*
+#...
+.*R_LARCH_ALIGN.*
+#...
diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
new file mode 100644
index 00000000000..19555a7148e
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
@@ -0,0 +1,12 @@ 
+# Add an align section before text section when ld -r.
+# There will be two R_LARCH_ALIGN relocations after ld -r.
+.text
+  addi.d $a0, $a0, 1
+  call36 func
+  .align 3
+func:
+  addi.d $a0, $a0, 1
+
+# Only add a align section for code section.
+.data
+  .4byte 0x1234
diff --git a/ld/testsuite/ld-loongarch-elf/relax.exp b/ld/testsuite/ld-loongarch-elf/relax.exp
index 7c32a65244c..066f1893042 100644
--- a/ld/testsuite/ld-loongarch-elf/relax.exp
+++ b/ld/testsuite/ld-loongarch-elf/relax.exp
@@ -46,6 +46,10 @@  proc run_partial_linking_align_test {} {
   }
 }
 
+if [istarget loongarch*-*-*] {
+  run_dump_test "relax-align-ld-r"
+}
+
 if [istarget loongarch64-*-*] {
   if [isbuild loongarch64-*-*] {
     run_dump_test "relax-align-ignore-start"