[v5,3/5] LoongArch: Add tls transition support.

Message ID 20231222114243.1836112-4-cailulu@loongson.cn
State Unresolved
Headers
Series Add support for TLS Descriptors (TLSDESC) |

Checks

Context Check Description
snail/binutils-gdb-check warning Git am fail log

Commit Message

Lulu Cai Dec. 22, 2023, 11:42 a.m. UTC
  Transitions between DESC->IE/LE and IE->LE are supported now.
1. For DESC -> LE:
   pcalau12i  $a0,%desc_pc_hi20(var)     =>  lu12i.w $a0,%le_hi20(var)
   addi.d     $a0,$a0,%desc_pc_lo12(var) =>  ori $a0,$a0,%le_lo12(var)
   ld.d       $a1,$a0,%desc_ld(var)      =>  NOP
   jirl       $ra,$a1,%desc_call(var)	 =>  NOP
   add.d      $a0,$a0,$tp
2. For DESC -> IE:
   pcalau12i  $a0,%desc_pc_hi20(var)     =>  pcalau12i $a0,%ie_pc_hi20(var)
   addi.d     $a0,$a0,%desc_pc_lo12(var) =>  ld.d $a0,$a0,%ie_pc_lo12(var)
   ld.d       $a1,$a0,%desc_ld(var)      =>  NOP
   jirl       $ra,$a1,%desc_call(var)	 =>  NOP
   add.d      $a0,$a0,$tp
3. For IE -> LE:
   pcalau12i  $a0,%ie_pc_hi20(var)       =>  lu12i.w $a0,%le_hi20(var)
   ld.d       $a0,$a0,%ie_pc_lo12(var)   =>  ori $a0,$a0,%le_lo12(var)
   add.d      $a0,$a0,$tp
4. When a tls variable is accessed using both DESC and IE, DESC transitions
   to IE and uses the same GOT entry as IE.
---
 bfd/elfnn-loongarch.c      | 216 ++++++++++++++++++++++++++++++++++++-
 include/opcode/loongarch.h |   6 ++
 2 files changed, 221 insertions(+), 1 deletion(-)
  

Comments

Lulu Cai Dec. 29, 2023, 10:31 a.m. UTC | #1
On 2023/12/28 at 10:42 PM, Tatsuyuki Ishi Wrote:
>> On Dec 22, 2023, at 20:42, Lulu Cai <cailulu@loongson.cn> wrote:
>>
>> Transitions between DESC->IE/LE and IE->LE are supported now.
>> 1. For DESC -> LE:
>>   pcalau12i  $a0,%desc_pc_hi20(var)     =>  lu12i.w $a0,%le_hi20(var)
>>   addi.d     $a0,$a0,%desc_pc_lo12(var) =>  ori $a0,$a0,%le_lo12(var)
>>   ld.d       $a1,$a0,%desc_ld(var)      =>  NOP
>>   jirl       $ra,$a1,%desc_call(var)=>  NOP
>>   add.d      $a0,$a0,$tp
>> 2. For DESC -> IE:
>>   pcalau12i  $a0,%desc_pc_hi20(var)     =>  pcalau12i 
>> $a0,%ie_pc_hi20(var)
>>   addi.d     $a0,$a0,%desc_pc_lo12(var) =>  ld.d $a0,$a0,%ie_pc_lo12(var)
>>   ld.d       $a1,$a0,%desc_ld(var)      =>  NOP
>>   jirl       $ra,$a1,%desc_call(var)=>  NOP
>>   add.d      $a0,$a0,$tp
>> 3. For IE -> LE:
>>   pcalau12i  $a0,%ie_pc_hi20(var)       =>  lu12i.w $a0,%le_hi20(var)
>>   ld.d       $a0,$a0,%ie_pc_lo12(var)   =>  ori $a0,$a0,%le_lo12(var)
>>   add.d      $a0,$a0,$tp
>> 4. When a tls variable is accessed using both DESC and IE, DESC 
>> transitions
>>   to IE and uses the same GOT entry as IE.
>> ---
>> bfd/elfnn-loongarch.c      | 216 ++++++++++++++++++++++++++++++++++++-
>> include/opcode/loongarch.h |   6 ++
>> 2 files changed, 221 insertions(+), 1 deletion(-)
>>
>> diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
>> index 95a39148f73..1347d13d2e2 100644
>> --- a/bfd/elfnn-loongarch.c
>> +++ b/bfd/elfnn-loongarch.c
>> @@ -145,6 +145,16 @@ struct loongarch_elf_link_hash_table
>> #define elf_backend_rela_normal 1
>> #define elf_backend_default_execstack 0
>>
>> +#define IS_LOONGARCH_TLS_DESC_RELOC(R_TYPE)    \
>> +  ((R_TYPE) == R_LARCH_TLS_DESC_PC_HI20\
>> +   || (R_TYPE) == R_LARCH_TLS_DESC_PC_LO12  \
>> +   || (R_TYPE) == R_LARCH_TLS_DESC_LD \
>> +   || (R_TYPE) == R_LARCH_TLS_DESC_CALL)
>> +
>> +#define IS_LOONGARCH_TLS_IE_RELOC(R_TYPE) \
>> +  ((R_TYPE) == R_LARCH_TLS_IE_PC_HI20 \
>> +   || (R_TYPE) == R_LARCH_TLS_IE_PC_LO12)
>> +
>> /* Generate a PLT header.  */
>>
>> static bool
>> @@ -593,6 +603,10 @@ loongarch_elf_record_tls_and_got_reference (bfd 
>> *abfd,
>>
>>   char *new_tls_type = &_bfd_loongarch_elf_tls_type (abfd, h, symndx);
>>   *new_tls_type |= tls_type;
>> +
>> +  /* If a symbol is accessed by both IE and DESC, relax DESC to IE.  */
>> +  if ((*new_tls_type & GOT_TLS_IE) && (*new_tls_type & GOT_TLS_GDESC))
>> +    *new_tls_type &= ~ (GOT_TLS_GDESC);
>>   if ((*new_tls_type & GOT_NORMAL) && (*new_tls_type & ~GOT_NORMAL))
>>     {
>>       _bfd_error_handler (_("%pB: `%s' accessed both as normal and "
>> @@ -605,6 +619,104 @@ loongarch_elf_record_tls_and_got_reference (bfd 
>> *abfd,
>>   return true;
>> }
>>
>> +static unsigned int
>> +loongarch_reloc_got_type (unsigned int r_type)
>> +{
>> +  switch (r_type)
>> +    {
>> +      case R_LARCH_TLS_DESC_PC_HI20:
>> +      case R_LARCH_TLS_DESC_PC_LO12:
>> +      case R_LARCH_TLS_DESC_LD:
>> +      case R_LARCH_TLS_DESC_CALL:
>> +return GOT_TLS_GDESC;
>> +
>> +      case R_LARCH_TLS_IE_PC_HI20:
>> +      case R_LARCH_TLS_IE_PC_LO12:
>> +return GOT_TLS_IE;
>> +
>> +      default:
>> +break;
>

> I would expect the function to cover GD and LE too. See remark about 
> relaxation below though.
>

I may add it in a later version.

>> +    }
>> +  return GOT_UNKNOWN;
>> +}
>> +
>> +/* Return true if tls type transition can be performed.  */
>> +static bool
>> +loongarch_can_relax_tls (struct bfd_link_info *info, unsigned int 
>> r_type,
>> +struct elf_link_hash_entry *h, bfd *input_bfd,
>> +unsigned long r_symndx)
>> +{
>> +  char symbol_tls_type;
>> +  unsigned int reloc_got_type;
>> +
>> +  if (! (IS_LOONGARCH_TLS_DESC_RELOC (r_type)
>> +|| IS_LOONGARCH_TLS_IE_RELOC (r_type)))
>> +    return false;
>> +
>> +  symbol_tls_type = _bfd_loongarch_elf_tls_type (input_bfd, h, 
>> r_symndx);
>> +  reloc_got_type = loongarch_reloc_got_type (r_type);
>> +
>> +  if (symbol_tls_type == GOT_TLS_IE && GOT_TLS_GD_ANY_P 
>> (reloc_got_type))
>> +    return true;
>

> Per the suggestion above, if you add TLS_GD into the return value of 
> loongarch_reloc_got_type, you might want to double check if GD -> IE 
> relaxation is possible. (This was not the case for RISC-V.)

GD->LE relaxation is not possible on LoongArch because the location of 
bl %plt(__tls_get_addr) cannot be determined.

>
>> +
>> +  if (! bfd_link_executable (info))
>> +      return false;
>> +
>> +  if (h && h->root.type == bfd_link_hash_undefweak)
>> +    return false;
>> +
>> +  return true;
>> +}
>> +
>> +/* The type of relocation that can be transitioned.  */
>> +static unsigned int
>> +loongarch_tls_transition_without_check (struct bfd_link_info *info,
>> +unsigned int r_type,
>> +struct elf_link_hash_entry *h)
>> +{
>> +  bool local_exec = bfd_link_executable (info)
>> +   && SYMBOL_REFERENCES_LOCAL (info, h);
>> +
>> +  switch (r_type)
>> +    {
>> +      case R_LARCH_TLS_DESC_PC_HI20:
>> +return (local_exec
>> +? R_LARCH_TLS_LE_HI20
>> +: R_LARCH_TLS_IE_PC_HI20);
>> +
>> +      case R_LARCH_TLS_DESC_PC_LO12:
>> +return (local_exec
>> +? R_LARCH_TLS_LE_LO12
>> +: R_LARCH_TLS_IE_PC_LO12);
>> +
>> +      case R_LARCH_TLS_DESC_LD:
>> +      case R_LARCH_TLS_DESC_CALL:
>> +return R_LARCH_NONE;
>> +
>> +      case R_LARCH_TLS_IE_PC_HI20:
>> +return local_exec ? R_LARCH_TLS_LE_HI20 : r_type;
>> +
>> +      case R_LARCH_TLS_IE_PC_LO12:
>> +return local_exec ? R_LARCH_TLS_LE_LO12 : r_type;
>> +
>> +      default:
>> +break;
>> +    }
>> +
>> +  return r_type;
>> +}
>> +
>> +static unsigned int
>> +loongarch_tls_transition (struct bfd_link_info *info, unsigned int 
>> r_type,
>> + struct elf_link_hash_entry *h, bfd *input_bfd,
>> + unsigned long r_symndx)
>> +{
>> +  if (! loongarch_can_relax_tls (info, r_type, h, input_bfd,r_symndx))
>> +    return r_type;
>> +
>> +  return loongarch_tls_transition_without_check (info, r_type, h);
>> +}
>> +
>> /* Look through the relocs for a section during the first phase, and
>>    allocate space in the global offset table or procedure linkage
>>    table.  */
>> @@ -706,6 +818,7 @@ loongarch_elf_check_relocs (bfd *abfd, struct 
>> bfd_link_info *info,
>>       int need_dynreloc = 0;
>>       int only_need_pcrel = 0;
>>
>> +      r_type = loongarch_tls_transition (info, r_type, h, abfd, 
>> r_symndx);
>>       switch (r_type)
>> {
>> case R_LARCH_GOT_PC_HI20:
>> @@ -2403,6 +2516,96 @@ loongarch_reloc_is_fatal (struct bfd_link_info 
>> *info,
>>       relocation += 0x100000000;\
>>   })
>>
>> +/* Transition instruction sequence to relax instruction sequence.  */
>> +static bool
>> +loongarch_tls_relax (bfd *abfd, asection *sec, Elf_Internal_Rela *rel,
>> +   int r_type, struct elf_link_hash_entry *h,
>> +   struct bfd_link_info *info)
>> +{
>> +  bool local_exec = bfd_link_executable (info)
>> +   && SYMBOL_REFERENCES_LOCAL (info, h);
>> +  bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
>> +  unsigned long insn;
>> +
>> +  switch (r_type)
>> +    {
>> +      case R_LARCH_TLS_DESC_PC_HI20:
>> +if (local_exec)
>> +   /* DESC -> LE relaxation:
>> +      pcalalau12i $a0,%desc_pc_hi20(var) =>
>> +      lu12i.w $a0,%le_hi20(var)
>> +   */
>> +   bfd_put (32, abfd, LARCH_LU12I_W | LARCH_RD_A0,
>> +    contents + rel->r_offset);
>> +
>> +/* DESC -> IE relaxation:
>> +  pcalalau12i $a0,%desc_pc_hi20(var) =>
>> +  pcalalau12i $a0,%ie_pc_hi20(var)
>> +*/
>> +return true;
>> +
>> +      case R_LARCH_TLS_DESC_PC_LO12:
>> +if (local_exec)
>> + {
>> +   /* DESC -> LE relaxation:
>> +      addi.d $a0,$a0,%desc_pc_lo12(var) =>
>> +      ori  $a0,$a0,le_lo12(var)
>> +   */
>> +   insn = LARCH_ORI | LARCH_RD_RJ_A0;
>> +   bfd_put (32, abfd, LARCH_ORI | LARCH_RD_RJ_A0,
>> +    contents + rel->r_offset);
>> + }
>> +else
>> + {
>> +   /* DESC -> IE relaxation:
>> +      addi.d $a0,$a0,%desc_pc_lo12(var) =>
>> +      ld.d $a0,$a0,%%ie_pc_lo12
>> +   */
>> +   bfd_put (32, abfd, LARCH_LD_D | LARCH_RD_RJ_A0,
>> +    contents + rel->r_offset);
>> + }
>> +return true;
>> +
>> +      case R_LARCH_TLS_DESC_LD:
>> +      case R_LARCH_TLS_DESC_CALL:
>> +/* DESC -> LE/IE relaxation:
>> +  ld.d $ra,$a0,%desc_ld(var) => NOP
>> +  jirl $ra,$ra,%desc_call(var) => NOP
>> +*/
>> +bfd_put (32, abfd, LARCH_NOP, contents + rel->r_offset);
>> +return true;
>> +
>> +      case R_LARCH_TLS_IE_PC_HI20:
>> +if (local_exec)
>> + {
>> +   /* IE -> LE relaxation:
>> +      pcalalau12i $rd,%ie_pc_hi20(var) =>
>> +      lu12i.w $rd,%le_hi20(var)
>> +   */
>> +   insn = bfd_getl32 (contents + rel->r_offset);
>> +   bfd_put (32, abfd, LARCH_LU12I_W | (insn & 0x1f),
>> +    contents + rel->r_offset);
>> + }
>> +return true;
>> +
>> +      case R_LARCH_TLS_IE_PC_LO12:
>> +if (local_exec)
>> + {
>> +   /* IE -> LE relaxation:
>> +      ld.d $rd,$rj,%%ie_pc_lo12 =>
>> +      ori  $rd,$rj,le_lo12(var)
>> +   */
>> +   insn = bfd_getl32 (contents + rel->r_offset);
>> +   bfd_put (32, abfd, LARCH_ORI | (insn & 0x3ff),
>> +    contents + rel->r_offset);
>> + }
>> +return true;
>> +    }
>> +
>> +  return false;
>> +}
>> +
>> +
>> static int
>> loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info 
>> *info,
>> bfd *input_bfd, asection *input_section,
>> @@ -2426,7 +2629,7 @@ loongarch_elf_relocate_section (bfd 
>> *output_bfd, struct bfd_link_info *info,
>>   relend = relocs + input_section->reloc_count;
>>   for (rel = relocs; rel < relend; rel++)
>>     {
>> -      int r_type = ELFNN_R_TYPE (rel->r_info);
>> +      unsigned int r_type = ELFNN_R_TYPE (rel->r_info);
>>       unsigned long r_symndx = ELFNN_R_SYM (rel->r_info);
>>       bfd_vma pc = sec_addr (input_section) + rel->r_offset;
>>       reloc_howto_type *howto = NULL;
>> @@ -2436,6 +2639,7 @@ loongarch_elf_relocate_section (bfd 
>> *output_bfd, struct bfd_link_info *info,
>>       const char *name;
>>       bfd_reloc_status_type r = bfd_reloc_ok;
>>       bool is_ie, is_desc, is_undefweak, unresolved_reloc, defined_local;
>> +      unsigned int relaxed_r_type;
>>       bool resolved_local, resolved_dynly, resolved_to_const;
>>       char tls_type;
>>       bfd_vma relocation, off, ie_off, desc_off;
>> @@ -2567,6 +2771,16 @@ loongarch_elf_relocate_section (bfd 
>> *output_bfd, struct bfd_link_info *info,
>>
>>       BFD_ASSERT (!resolved_local || defined_local);
>>
>> +      relaxed_r_type = loongarch_tls_transition (info, r_type, h, 
>> input_bfd, r_symndx);
>> +      if (relaxed_r_type != r_type)
>> +      {
>> +howto = loongarch_elf_rtype_to_howto (input_bfd, relaxed_r_type);
>> +BFD_ASSERT (howto != NULL);
>> +
>> +if (loongarch_tls_relax (input_bfd, input_section, rel, r_type, h, 
>> info))
>> + r_type = relaxed_r_type;
>> +      }
>> +
>>       is_desc = false;
>>       is_ie = false;
>>       switch (r_type)
>> diff --git a/include/opcode/loongarch.h b/include/opcode/loongarch.h
>> index da936f7945a..32ff4d8a0f1 100644
>> --- a/include/opcode/loongarch.h
>> +++ b/include/opcode/loongarch.h
>> @@ -42,6 +42,12 @@ extern "C"
>>     ((value) < (-(1 << ((bits) - 1) << align)) \
>>       || (value) > ((((1 << ((bits) - 1)) - 1) << align)))
>>
>> +  #define LARCH_LU12I_W 0x14000000
>> +  #define LARCH_ORI 0x03800000
>> +  #define LARCH_LD_D 0x28c00000
>> +  #define LARCH_RD_A0 0x04
>> +  #define LARCH_RD_RJ_A0 0x084
>> +
>>   typedef uint32_t insn_t;
>>
>>   struct loongarch_opcode
>> -- 
>> 2.43.0
>>
>>
>
  

Patch

diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index 95a39148f73..1347d13d2e2 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -145,6 +145,16 @@  struct loongarch_elf_link_hash_table
 #define elf_backend_rela_normal 1
 #define elf_backend_default_execstack 0
 
+#define IS_LOONGARCH_TLS_DESC_RELOC(R_TYPE)    \
+  ((R_TYPE) == R_LARCH_TLS_DESC_PC_HI20	\
+   || (R_TYPE) == R_LARCH_TLS_DESC_PC_LO12  \
+   || (R_TYPE) == R_LARCH_TLS_DESC_LD \
+   || (R_TYPE) == R_LARCH_TLS_DESC_CALL)
+
+#define IS_LOONGARCH_TLS_IE_RELOC(R_TYPE) \
+  ((R_TYPE) == R_LARCH_TLS_IE_PC_HI20 \
+   || (R_TYPE) == R_LARCH_TLS_IE_PC_LO12)
+
 /* Generate a PLT header.  */
 
 static bool
@@ -593,6 +603,10 @@  loongarch_elf_record_tls_and_got_reference (bfd *abfd,
 
   char *new_tls_type = &_bfd_loongarch_elf_tls_type (abfd, h, symndx);
   *new_tls_type |= tls_type;
+
+  /* If a symbol is accessed by both IE and DESC, relax DESC to IE.  */
+  if ((*new_tls_type & GOT_TLS_IE) && (*new_tls_type & GOT_TLS_GDESC))
+    *new_tls_type &= ~ (GOT_TLS_GDESC);
   if ((*new_tls_type & GOT_NORMAL) && (*new_tls_type & ~GOT_NORMAL))
     {
       _bfd_error_handler (_("%pB: `%s' accessed both as normal and "
@@ -605,6 +619,104 @@  loongarch_elf_record_tls_and_got_reference (bfd *abfd,
   return true;
 }
 
+static unsigned int
+loongarch_reloc_got_type (unsigned int r_type)
+{
+  switch (r_type)
+    {
+      case R_LARCH_TLS_DESC_PC_HI20:
+      case R_LARCH_TLS_DESC_PC_LO12:
+      case R_LARCH_TLS_DESC_LD:
+      case R_LARCH_TLS_DESC_CALL:
+	return GOT_TLS_GDESC;
+
+      case R_LARCH_TLS_IE_PC_HI20:
+      case R_LARCH_TLS_IE_PC_LO12:
+	return GOT_TLS_IE;
+
+      default:
+	break;
+    }
+  return GOT_UNKNOWN;
+}
+
+/* Return true if tls type transition can be performed.  */
+static bool
+loongarch_can_relax_tls (struct bfd_link_info *info, unsigned int r_type,
+			 struct elf_link_hash_entry *h, bfd *input_bfd,
+			 unsigned long r_symndx)
+{
+  char symbol_tls_type;
+  unsigned int reloc_got_type;
+
+  if (! (IS_LOONGARCH_TLS_DESC_RELOC (r_type)
+	 || IS_LOONGARCH_TLS_IE_RELOC (r_type)))
+    return false;
+
+  symbol_tls_type = _bfd_loongarch_elf_tls_type (input_bfd, h, r_symndx);
+  reloc_got_type = loongarch_reloc_got_type (r_type);
+
+  if (symbol_tls_type == GOT_TLS_IE && GOT_TLS_GD_ANY_P (reloc_got_type))
+    return true;
+
+  if (! bfd_link_executable (info))
+      return false;
+
+  if (h && h->root.type == bfd_link_hash_undefweak)
+    return false;
+
+  return true;
+}
+
+/* The type of relocation that can be transitioned.  */
+static unsigned int
+loongarch_tls_transition_without_check (struct bfd_link_info *info,
+					unsigned int r_type,
+					struct elf_link_hash_entry *h)
+{
+  bool local_exec = bfd_link_executable (info)
+		    && SYMBOL_REFERENCES_LOCAL (info, h);
+
+  switch (r_type)
+    {
+      case R_LARCH_TLS_DESC_PC_HI20:
+	return (local_exec
+		? R_LARCH_TLS_LE_HI20
+		: R_LARCH_TLS_IE_PC_HI20);
+
+      case R_LARCH_TLS_DESC_PC_LO12:
+	return (local_exec
+		? R_LARCH_TLS_LE_LO12
+		: R_LARCH_TLS_IE_PC_LO12);
+
+      case R_LARCH_TLS_DESC_LD:
+      case R_LARCH_TLS_DESC_CALL:
+	return R_LARCH_NONE;
+
+      case R_LARCH_TLS_IE_PC_HI20:
+	return local_exec ? R_LARCH_TLS_LE_HI20 : r_type;
+
+      case R_LARCH_TLS_IE_PC_LO12:
+	return local_exec ? R_LARCH_TLS_LE_LO12 : r_type;
+
+      default:
+	break;
+    }
+
+  return r_type;
+}
+
+static unsigned int
+loongarch_tls_transition (struct bfd_link_info *info, unsigned int r_type,
+			  struct elf_link_hash_entry *h, bfd *input_bfd,
+			  unsigned long r_symndx)
+{
+  if (! loongarch_can_relax_tls (info, r_type, h, input_bfd,r_symndx))
+    return r_type;
+
+  return loongarch_tls_transition_without_check (info, r_type, h);
+}
+
 /* Look through the relocs for a section during the first phase, and
    allocate space in the global offset table or procedure linkage
    table.  */
@@ -706,6 +818,7 @@  loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
       int need_dynreloc = 0;
       int only_need_pcrel = 0;
 
+      r_type = loongarch_tls_transition (info, r_type, h, abfd, r_symndx);
       switch (r_type)
 	{
 	case R_LARCH_GOT_PC_HI20:
@@ -2403,6 +2516,96 @@  loongarch_reloc_is_fatal (struct bfd_link_info *info,
       relocation += 0x100000000;			\
   })
 
+/* Transition instruction sequence to relax instruction sequence.  */
+static bool
+loongarch_tls_relax (bfd *abfd, asection *sec, Elf_Internal_Rela *rel,
+		    int r_type, struct elf_link_hash_entry *h,
+		    struct bfd_link_info *info)
+{
+  bool local_exec = bfd_link_executable (info)
+		    && SYMBOL_REFERENCES_LOCAL (info, h);
+  bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
+  unsigned long insn;
+
+  switch (r_type)
+    {
+      case R_LARCH_TLS_DESC_PC_HI20:
+	if (local_exec)
+	    /* DESC -> LE relaxation:
+	       pcalalau12i $a0,%desc_pc_hi20(var) =>
+	       lu12i.w $a0,%le_hi20(var)
+	    */
+	    bfd_put (32, abfd, LARCH_LU12I_W | LARCH_RD_A0,
+		     contents + rel->r_offset);
+
+	/* DESC -> IE relaxation:
+	   pcalalau12i $a0,%desc_pc_hi20(var) =>
+	   pcalalau12i $a0,%ie_pc_hi20(var)
+	*/
+	return true;
+
+      case R_LARCH_TLS_DESC_PC_LO12:
+	if (local_exec)
+	  {
+	    /* DESC -> LE relaxation:
+	       addi.d $a0,$a0,%desc_pc_lo12(var) =>
+	       ori  $a0,$a0,le_lo12(var)
+	    */
+	    insn = LARCH_ORI | LARCH_RD_RJ_A0;
+	    bfd_put (32, abfd, LARCH_ORI | LARCH_RD_RJ_A0,
+		     contents + rel->r_offset);
+	  }
+	else
+	  {
+	    /* DESC -> IE relaxation:
+	       addi.d $a0,$a0,%desc_pc_lo12(var) =>
+	       ld.d $a0,$a0,%%ie_pc_lo12
+	    */
+	    bfd_put (32, abfd, LARCH_LD_D | LARCH_RD_RJ_A0,
+		     contents + rel->r_offset);
+	  }
+	return true;
+
+      case R_LARCH_TLS_DESC_LD:
+      case R_LARCH_TLS_DESC_CALL:
+	/* DESC -> LE/IE relaxation:
+	   ld.d $ra,$a0,%desc_ld(var) => NOP
+	   jirl $ra,$ra,%desc_call(var) => NOP
+	*/
+	bfd_put (32, abfd, LARCH_NOP, contents + rel->r_offset);
+	return true;
+
+      case R_LARCH_TLS_IE_PC_HI20:
+	if (local_exec)
+	  {
+	    /* IE -> LE relaxation:
+	       pcalalau12i $rd,%ie_pc_hi20(var) =>
+	       lu12i.w $rd,%le_hi20(var)
+	    */
+	    insn = bfd_getl32 (contents + rel->r_offset);
+	    bfd_put (32, abfd, LARCH_LU12I_W | (insn & 0x1f),
+		     contents + rel->r_offset);
+	  }
+	return true;
+
+      case R_LARCH_TLS_IE_PC_LO12:
+	if (local_exec)
+	  {
+	    /* IE -> LE relaxation:
+	       ld.d $rd,$rj,%%ie_pc_lo12 =>
+	       ori  $rd,$rj,le_lo12(var)
+	    */
+	    insn = bfd_getl32 (contents + rel->r_offset);
+	    bfd_put (32, abfd, LARCH_ORI | (insn & 0x3ff),
+		     contents + rel->r_offset);
+	  }
+	return true;
+    }
+
+  return false;
+}
+
+
 static int
 loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 				bfd *input_bfd, asection *input_section,
@@ -2426,7 +2629,7 @@  loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
   relend = relocs + input_section->reloc_count;
   for (rel = relocs; rel < relend; rel++)
     {
-      int r_type = ELFNN_R_TYPE (rel->r_info);
+      unsigned int r_type = ELFNN_R_TYPE (rel->r_info);
       unsigned long r_symndx = ELFNN_R_SYM (rel->r_info);
       bfd_vma pc = sec_addr (input_section) + rel->r_offset;
       reloc_howto_type *howto = NULL;
@@ -2436,6 +2639,7 @@  loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       const char *name;
       bfd_reloc_status_type r = bfd_reloc_ok;
       bool is_ie, is_desc, is_undefweak, unresolved_reloc, defined_local;
+      unsigned int relaxed_r_type;
       bool resolved_local, resolved_dynly, resolved_to_const;
       char tls_type;
       bfd_vma relocation, off, ie_off, desc_off;
@@ -2567,6 +2771,16 @@  loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 
       BFD_ASSERT (!resolved_local || defined_local);
 
+      relaxed_r_type = loongarch_tls_transition (info, r_type, h, input_bfd, r_symndx);
+      if (relaxed_r_type != r_type)
+      {
+	howto = loongarch_elf_rtype_to_howto (input_bfd, relaxed_r_type);
+	BFD_ASSERT (howto != NULL);
+
+	if (loongarch_tls_relax (input_bfd, input_section, rel, r_type, h, info))
+	  r_type = relaxed_r_type;
+      }
+
       is_desc = false;
       is_ie = false;
       switch (r_type)
diff --git a/include/opcode/loongarch.h b/include/opcode/loongarch.h
index da936f7945a..32ff4d8a0f1 100644
--- a/include/opcode/loongarch.h
+++ b/include/opcode/loongarch.h
@@ -42,6 +42,12 @@  extern "C"
     ((value) < (-(1 << ((bits) - 1) << align)) 	\
       || (value) > ((((1 << ((bits) - 1)) - 1) << align)))
 
+  #define LARCH_LU12I_W 0x14000000
+  #define LARCH_ORI 0x03800000
+  #define LARCH_LD_D 0x28c00000
+  #define LARCH_RD_A0 0x04
+  #define LARCH_RD_RJ_A0 0x084
+
   typedef uint32_t insn_t;
 
   struct loongarch_opcode