[v1,1/2] LoongArch: Modify the method of obtaining symbolic addresses.

Message ID 20220719130852.2011955-2-chenglulu@loongson.cn
State New
Headers
Series LoongArch: Modify the method of obtaining symbolic addresses. |

Commit Message

Lulu Cheng July 19, 2022, 1:08 p.m. UTC
  1. The original LA macro instruction is split into two instructions to
   obtain the address of the symbol if enable '-mexplicit-relocs'.
2. Currently, '-mcmodel=' only supports 'normal' mode, because other mode
   behaviors are not yet determined. This function is gradually improved
   by the subsequent handling.
3. Modify the method that calls global functions. From 'la.global + jirl'
   to 'bl'.

gcc/ChangeLog:

	* common/config/loongarch/loongarch-common.cc:
	Enable '-fsection-anchors' when O1 and more advanced optimization.
	* config/loongarch/constraints.md (a): Delete the constraint.
	(b): A constant call not local address.
	(h): Delete the constraint.
	(t): Delete the constraint.
	* config/loongarch/genopts/loongarch.opt.in: Add new options
	'-mexplicit-relocs', and enable by default.
	* config/loongarch/loongarch-protos.h (enum loongarch_symbol_type):
	Add new symbol type 'SYMBOL_PCREL', 'SYMBOL_TLS_IE' and 'SYMBOL_TLS_LE'.
	(loongarch_split_move_insn_p): Delete function declaration.
	(loongarch_split_move_insn): Delete function declaration.
	* config/loongarch/loongarch.cc (enum loongarch_address_type):
	Add new address type 'ADDRESS_LO_SUM'.
	(loongarch_symbol_binds_local_p):
	(loongarch_classify_symbol): Return symbol type. If symbol is a label,
	or symbol is a local symbol return SYMBOL_PCREL. If is a tls symbol,
	return SYMBOL_TLS. If is a not local symbol return SYMBOL_GOT_DISP.
	(loongarch_classify_symbolic_expression): New function definitions.
	Classify the base of symbolic expression X, given that X appears in
	context CONTEXT.
	(loongarch_symbolic_constant_p): Add handling of 'SYMBOL_TLS_IE'
	'SYMBOL_TLS_LE' and 'SYMBOL_PCREL'.
	(loongarch_symbol_insns): Add handling of 'SYMBOL_TLS_IE' 'SYMBOL_TLS_LE'
	and 'SYMBOL_PCREL'.
	(loongarch_split_symbol_type): New function definitions.
	Determines whether the symbol load should be split into two instructions.
	(loongarch_valid_lo_sum_p): New function definitions.
	Return true if a LO_SUM can address a value of mode MODE when the LO_SUM
	symbol has type SYMBOL_TYPE.
	(loongarch_classify_address): Add handling of 'LO_SUM'.
	(loongarch_address_insns): Add handling of 'ADDRESS_LO_SUM'.
	(loongarch_12bit_offset_address_p): Return true if address type is ADDRESS_LO_SUM.
	(loongarch_const_insns): Add handling of 'HIGH'.
	(loongarch_split_move_insn_p): Add the static attribute to the function.
	(loongarch_emit_set): New function definitions.
	(loongarch_call_tls_get_addr): Add symbol handling when defining the
	TARGET_EXPLICIT_RELOCS macro.
	(loongarch_legitimize_tls_address): Add symbol handling when defining the
	TARGET_EXPLICIT_RELOCS macro.
	(loongarch_split_symbol): New function definitions. Split symbol.
	(loongarch_legitimize_address): Add codes see if the address can split into a high part
	and a LO_SUM.
	(loongarch_legitimize_const_move): Add codes split moves of symbolic constants into
	high and low.
	(loongarch_split_move_insn): Delete function definitions.
	(loongarch_output_move):
	(loongarch_print_operand_reloc): New function definitions.
	Print symbolic operand OP, which is part of a HIGH or LO_SUM in context CONTEXT.
	(loongarch_print_operand): Rearrange alphabetical order and add H and L to support HIGH
	and LOW output.
	(loongarch_print_operand_address): Add handling of 'ADDRESS_LO_SUM'.
	(TARGET_MIN_ANCHOR_OFFSET): Define macro to -IMM_REACH/2.
	(TARGET_MAX_ANCHOR_OFFSET): Define macro to IMM_REACH/2-1.
	* config/loongarch/loongarch.h (LARCH_U12BIT_OFFSET_P):
	Rename to LARCH_12BIT_OFFSET_P.
	(LARCH_12BIT_OFFSET_P): New macro.
	* config/loongarch/loongarch.md (movti): Delete the template.
	(*movti): Delete the template.
	(movtf): Delete the template.
	(*movtf): Delete the template.
	(*low<mode>): New template of normal symbol low address.
	(@tls_low<mode>): New template of tls symbol low address.
	(@ld_from_got<mode>): New template load address from got table.
	(@ori_l_lo12<mode>): New template.
	* config/loongarch/loongarch.opt: Update.
	* config/loongarch/predicates.md (is_const_call_weak_symbol):
	Delete the predicate.
	(is_const_call_plt_symbol): Delete the predicate.
	(is_const_call_global_noplt_symbol): Delete the predicate.
	(is_const_call_no_local_symbol): New predicate, Determines whether it is a local symbol
	or label.
---
 .../config/loongarch/loongarch-common.cc      |   1 +
 gcc/config/loongarch/constraints.md           |  24 +-
 gcc/config/loongarch/genopts/loongarch.opt.in |   4 +
 gcc/config/loongarch/loongarch-protos.h       |   9 +-
 gcc/config/loongarch/loongarch.cc             | 660 +++++++++++++-----
 gcc/config/loongarch/loongarch.h              |   2 +-
 gcc/config/loongarch/loongarch.md             | 401 +++--------
 gcc/config/loongarch/loongarch.opt            |   4 +
 gcc/config/loongarch/predicates.md            |  56 +-
 9 files changed, 628 insertions(+), 533 deletions(-)
  

Comments

Xi Ruoyao July 19, 2022, 2:29 p.m. UTC | #1
The change seems too large.  It would be better to split it into
multiple commits (for example, just 3 commits for 1,2,3 below).

On Tue, 2022-07-19 at 21:08 +0800, Lulu Cheng wrote:

> 1. The original LA macro instruction is split into two instructions to
>    obtain the address of the symbol if enable '-mexplicit-relocs'.

It's better to add some test cases (with dg-final scan-assembler) for
this.  The test case will also show humans the intended behavior after
the change.

> 3. Modify the method that calls global functions. From 'la.global + jirl'
>    to 'bl'.

Why?  Does it means we'll rely on the assembler to emit the correct
sequence for -fno-plt?  Then it would be better to use a pseudo mnemonic
like "call" instead of "bl" (because it's not a single "bl"
instruction).


/* snip */

>  static bool
>  loongarch_valid_index_p (struct loongarch_address_info *info, rtx x,
>                           machine_mode mode, bool strict_p)
> @@ -1881,6 +1978,26 @@ loongarch_classify_address (struct loongarch_address_info *info, rtx x,
>        info->offset = XEXP (x, 1);
>        return (loongarch_valid_base_register_p (info->reg, mode, strict_p)
>               && loongarch_valid_offset_p (info->offset, mode));
> +
> +    case LO_SUM:
> +      info->type = ADDRESS_LO_SUM;
> +      info->reg = XEXP (x, 0);
> +      info->offset = XEXP (x, 1);
> +      /* We have to trust the creator of the LO_SUM to do something vaguely
> +        sane.  Target-independent code that creates a LO_SUM should also
> +        create and verify the matching HIGH.  Target-independent code that
> +        adds an offset to a LO_SUM must prove that the offset will not
> +        induce a carry.  Failure to do either of these things would be
> +        a bug, and we are not required to check for it here.  The MIPS

Don't copy MIPS code.

> +static bool loongarch_split_move_insn_p (rtx dest, rtx src);

Nit: "loongarch_split_move_insn_p" shall start a new line.
  
Lulu Cheng July 20, 2022, 12:40 a.m. UTC | #2
在 2022/7/19 下午10:29, Xi Ruoyao 写道:
> The change seems too large.  It would be better to split it into
> multiple commits (for example, just 3 commits for 1,2,3 below).
>
> On Tue, 2022-07-19 at 21:08 +0800, Lulu Cheng wrote:
>
>> 1. The original LA macro instruction is split into two instructions to
>>     obtain the address of the symbol if enable '-mexplicit-relocs'.
> It's better to add some test cases (with dg-final scan-assembler) for
> this.  The test case will also show humans the intended behavior after
> the change.
I'm sorry, I'll add the test cases as soon as possible.
>> 3. Modify the method that calls global functions. From 'la.global + jirl'
>>     to 'bl'.
> Why?  Does it means we'll rely on the assembler to emit the correct
> sequence for -fno-plt?  Then it would be better to use a pseudo mnemonic
> like "call" instead of "bl" (because it's not a single "bl"
> instruction).
I have not described the behavior of noplt, but I will do so in the next 
submission.
  

Patch

diff --git a/gcc/common/config/loongarch/loongarch-common.cc b/gcc/common/config/loongarch/loongarch-common.cc
index ed3730fce8b..f8b4660fabf 100644
--- a/gcc/common/config/loongarch/loongarch-common.cc
+++ b/gcc/common/config/loongarch/loongarch-common.cc
@@ -34,6 +34,7 @@  along with GCC; see the file COPYING3.  If not see
 static const struct default_options loongarch_option_optimization_table[] =
 {
   { OPT_LEVELS_ALL, OPT_fasynchronous_unwind_tables, NULL, 1 },
+  { OPT_LEVELS_1_PLUS, OPT_fsection_anchors, NULL, 1 },
   { OPT_LEVELS_NONE, 0, NULL, 0 }
 };
 
diff --git a/gcc/config/loongarch/constraints.md b/gcc/config/loongarch/constraints.md
index d0bfddbd5a9..43cb7b5f0f5 100644
--- a/gcc/config/loongarch/constraints.md
+++ b/gcc/config/loongarch/constraints.md
@@ -20,14 +20,14 @@ 
 
 ;; Register constraints
 
-;; "a" "A constant call global and noplt address."
-;; "b" <-----unused
+;; "a" <-----unused
+;; "b" "A constant call not local address."
 ;; "c" "A constant call local address."
 ;; "d" <-----unused
 ;; "e" JIRL_REGS
 ;; "f" FP_REGS
 ;; "g" <-----unused
-;; "h" "A constant call plt address."
+;; "h" <-----unused
 ;; "i" "Matches a general integer constant." (Global non-architectural)
 ;; "j" SIBCALL_REGS
 ;; "k" "A memory operand whose address is formed by a base register and
@@ -42,7 +42,7 @@ 
 ;; "q" CSR_REGS
 ;; "r" GENERAL_REGS (Global non-architectural)
 ;; "s" "Matches a symbolic integer constant." (Global non-architectural)
-;; "t" "A constant call weak address"
+;; "t" <-----unused
 ;; "u" "A signed 52bit constant and low 32-bit is zero (for logic instructions)"
 ;; "v" "A signed 64-bit constant and low 44-bit is zero (for logic instructions)."
 ;; "w" "Matches any valid memory."
@@ -89,10 +89,10 @@ 
 ;; "<" "Matches a pre-dec or post-dec operand." (Global non-architectural)
 ;; ">" "Matches a pre-inc or post-inc operand." (Global non-architectural)
 
-(define_constraint "a"
+(define_constraint "b"
   "@internal
-   A constant call global and noplt address."
-  (match_operand 0 "is_const_call_global_noplt_symbol"))
+   A constant call no local address."
+  (match_operand 0 "is_const_call_no_local_symbol"))
 
 (define_constraint "c"
   "@internal
@@ -105,11 +105,6 @@  (define_register_constraint "e" "JIRL_REGS"
 (define_register_constraint "f" "TARGET_HARD_FLOAT ? FP_REGS : NO_REGS"
   "A floating-point register (if available).")
 
-(define_constraint "h"
-  "@internal
-   A constant call plt address."
-  (match_operand 0 "is_const_call_plt_symbol"))
-
 (define_register_constraint "j" "SIBCALL_REGS"
   "@internal")
 
@@ -134,11 +129,6 @@  (define_memory_constraint "m"
 (define_register_constraint "q" "CSR_REGS"
   "A general-purpose register except for $r0 and $r1 for lcsr.")
 
-(define_constraint "t"
-  "@internal
-   A constant call weak address."
-  (match_operand 0 "is_const_call_weak_symbol"))
-
 (define_constraint "u"
   "A signed 52bit constant and low 32-bit is zero (for logic instructions)."
   (and (match_code "const_int")
diff --git a/gcc/config/loongarch/genopts/loongarch.opt.in b/gcc/config/loongarch/genopts/loongarch.opt.in
index 61e7d72a0a1..6f39500935d 100644
--- a/gcc/config/loongarch/genopts/loongarch.opt.in
+++ b/gcc/config/loongarch/genopts/loongarch.opt.in
@@ -154,6 +154,10 @@  mmax-inline-memcpy-size=
 Target Joined RejectNegative UInteger Var(loongarch_max_inline_memcpy_size) Init(1024)
 -mmax-inline-memcpy-size=SIZE	Set the max size of memcpy to inline, default is 1024.
 
+mexplicit-relocs
+Target Var(TARGET_EXPLICIT_RELOCS) Init(1)
+Use %reloc() assembly operators.
+
 ; The code model option names for -mcmodel.
 Enum
 Name(cmodel) Type(int)
diff --git a/gcc/config/loongarch/loongarch-protos.h b/gcc/config/loongarch/loongarch-protos.h
index 2287fd3763c..5bbc43d92fb 100644
--- a/gcc/config/loongarch/loongarch-protos.h
+++ b/gcc/config/loongarch/loongarch-protos.h
@@ -27,9 +27,13 @@  along with GCC; see the file COPYING3.  If not see
    SYMBOL_GOT_DISP
        The symbol's value will be loaded directly from the GOT.
 
+   SYMBOL_PCREL
+       The symbol's value will be loaded directly from data section.
+
    SYMBOL_TLS
        A thread-local symbol.
 
+   SYMBOL_TLS_IE
    SYMBOL_TLSGD
    SYMBOL_TLSLDM
        UNSPEC wrappers around SYMBOL_TLS, corresponding to the
@@ -37,7 +41,10 @@  along with GCC; see the file COPYING3.  If not see
    */
 enum loongarch_symbol_type {
   SYMBOL_GOT_DISP,
+  SYMBOL_PCREL,
   SYMBOL_TLS,
+  SYMBOL_TLS_IE,
+  SYMBOL_TLS_LE,
   SYMBOL_TLSGD,
   SYMBOL_TLSLDM,
 };
@@ -71,8 +78,6 @@  extern rtx loongarch_legitimize_call_address (rtx);
 extern rtx loongarch_subword (rtx, bool);
 extern bool loongarch_split_move_p (rtx, rtx);
 extern void loongarch_split_move (rtx, rtx, rtx);
-extern bool loongarch_split_move_insn_p (rtx, rtx);
-extern void loongarch_split_move_insn (rtx, rtx, rtx);
 extern const char *loongarch_output_move (rtx, rtx);
 extern bool loongarch_cfun_has_cprestore_slot_p (void);
 #ifdef RTX_CODE
diff --git a/gcc/config/loongarch/loongarch.cc b/gcc/config/loongarch/loongarch.cc
index 8b0d7f459e0..3b28b6cef46 100644
--- a/gcc/config/loongarch/loongarch.cc
+++ b/gcc/config/loongarch/loongarch.cc
@@ -100,6 +100,10 @@  along with GCC; see the file COPYING3.  If not see
    ADDRESS_REG_REG
        A base register indexed by (optionally scaled) register.
 
+   ADDRESS_LO_SUM
+       A LO_SUM rtx.  The first operand is a valid base register and the second
+       operand is a symbolic address.
+
    ADDRESS_CONST_INT
        A signed 16-bit constant address.
 
@@ -109,24 +113,13 @@  enum loongarch_address_type
 {
   ADDRESS_REG,
   ADDRESS_REG_REG,
+  ADDRESS_LO_SUM,
   ADDRESS_CONST_INT,
   ADDRESS_SYMBOLIC
 };
 
 
-/* Information about an address described by loongarch_address_type.
-
-   ADDRESS_CONST_INT
-       No fields are used.
-
-   ADDRESS_REG
-       REG is the base register and OFFSET is the constant offset.
-
-   ADDRESS_REG_REG
-       A base register indexed by (optionally scaled) register.
-
-   ADDRESS_SYMBOLIC
-       SYMBOL_TYPE is the type of symbol that the address references.  */
+/* Information about an address described by loongarch_address_type.  */
 struct loongarch_address_info
 {
   enum loongarch_address_type type;
@@ -1617,11 +1610,12 @@  loongarch_weak_symbol_p (const_rtx x)
 bool
 loongarch_symbol_binds_local_p (const_rtx x)
 {
-  if (LABEL_REF_P (x))
+  if (SYMBOL_REF_P (x))
+    return (SYMBOL_REF_DECL (x)
+	    ? targetm.binds_local_p (SYMBOL_REF_DECL (x))
+	    : SYMBOL_REF_LOCAL_P (x));
+  else
     return false;
-
-  return (SYMBOL_REF_DECL (x) ? targetm.binds_local_p (SYMBOL_REF_DECL (x))
-			      : SYMBOL_REF_LOCAL_P (x));
 }
 
 /* Return true if rtx constants of mode MODE should be put into a small
@@ -1640,17 +1634,31 @@  static enum loongarch_symbol_type
 loongarch_classify_symbol (const_rtx x)
 {
   if (LABEL_REF_P (x))
-    return SYMBOL_GOT_DISP;
-
-  gcc_assert (SYMBOL_REF_P (x));
+    return SYMBOL_PCREL;
 
   if (SYMBOL_REF_TLS_MODEL (x))
     return SYMBOL_TLS;
 
-  if (SYMBOL_REF_P (x))
+  if (SYMBOL_REF_P (x)
+      && !loongarch_symbol_binds_local_p (x))
     return SYMBOL_GOT_DISP;
 
-  return SYMBOL_GOT_DISP;
+  return SYMBOL_PCREL;
+}
+
+/* Classify the base of symbolic expression X, given that X appears in
+   context CONTEXT.  */
+
+static enum loongarch_symbol_type
+loongarch_classify_symbolic_expression (rtx x)
+{
+  rtx offset;
+
+  split_const (x, &x, &offset);
+  if (UNSPEC_ADDRESS_P (x))
+    return UNSPEC_ADDRESS_TYPE (x);
+
+  return loongarch_classify_symbol (x);
 }
 
 /* Return true if X is a symbolic constant.  If it is,
@@ -1683,9 +1691,15 @@  loongarch_symbolic_constant_p (rtx x, enum loongarch_symbol_type *symbol_type)
      relocations.  */
   switch (*symbol_type)
     {
-    case SYMBOL_GOT_DISP:
+    case SYMBOL_TLS_IE:
+    case SYMBOL_TLS_LE:
     case SYMBOL_TLSGD:
     case SYMBOL_TLSLDM:
+    case SYMBOL_PCREL:
+      /* GAS rejects offsets outside the range [-2^31, 2^31-1].  */
+      return sext_hwi (INTVAL (offset), 32) == INTVAL (offset);
+
+    case SYMBOL_GOT_DISP:
     case SYMBOL_TLS:
       return false;
     }
@@ -1702,14 +1716,19 @@  loongarch_symbol_insns (enum loongarch_symbol_type type, machine_mode mode)
     case SYMBOL_GOT_DISP:
       /* The constant will have to be loaded from the GOT before it
 	 is used in an address.  */
-      if (mode != MAX_MACHINE_MODE)
+      if (!TARGET_EXPLICIT_RELOCS && mode != MAX_MACHINE_MODE)
 	return 0;
 
       return 3;
 
+    case SYMBOL_PCREL:
+    case SYMBOL_TLS_IE:
+    case SYMBOL_TLS_LE:
+      return 2;
+
     case SYMBOL_TLSGD:
     case SYMBOL_TLSLDM:
-      return 1;
+      return 3;
 
     case SYMBOL_TLS:
       /* We don't treat a bare TLS symbol as a constant.  */
@@ -1815,6 +1834,84 @@  loongarch_valid_offset_p (rtx x, machine_mode mode)
   return true;
 }
 
+/* Should a symbol of type SYMBOL_TYPE should be split in two?  */
+
+bool
+loongarch_split_symbol_type (enum loongarch_symbol_type symbol_type)
+{
+  switch (symbol_type)
+    {
+    case SYMBOL_PCREL:
+    case SYMBOL_GOT_DISP:
+    case SYMBOL_TLS_IE:
+    case SYMBOL_TLS_LE:
+    case SYMBOL_TLSGD:
+    case SYMBOL_TLSLDM:
+      return true;
+
+    case SYMBOL_TLS:
+      return false;
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return true if a LO_SUM can address a value of mode MODE when the
+   LO_SUM symbol has type SYMBOL_TYPE.  */
+
+static bool
+loongarch_valid_lo_sum_p (enum loongarch_symbol_type symbol_type,
+			  machine_mode mode, rtx x)
+{
+  int align, size;
+
+  /* Check that symbols of type SYMBOL_TYPE can be used to access values
+     of mode MODE.  */
+  if (loongarch_symbol_insns (symbol_type, mode) == 0)
+    return false;
+
+  /* Check that there is a known low-part relocation.  */
+  if (!loongarch_split_symbol_type (symbol_type))
+    return false;
+
+  /* We can't tell size or alignment when we have BLKmode, so try extracing a
+     decl from the symbol if possible.  */
+  if (mode == BLKmode)
+    {
+      rtx offset;
+
+      /* Extract the symbol from the LO_SUM operand, if any.  */
+      split_const (x, &x, &offset);
+
+      /* Might be a CODE_LABEL.  We can compute align but not size for that,
+	 so don't bother trying to handle it.  */
+      if (!SYMBOL_REF_P (x))
+	return false;
+
+      /* Use worst case assumptions if we don't have a SYMBOL_REF_DECL.  */
+      align = (SYMBOL_REF_DECL (x)
+	       ? DECL_ALIGN (SYMBOL_REF_DECL (x))
+	       : 1);
+      size = (SYMBOL_REF_DECL (x) && DECL_SIZE (SYMBOL_REF_DECL (x))
+	      ? tree_to_uhwi (DECL_SIZE (SYMBOL_REF_DECL (x)))
+	      : 2*BITS_PER_WORD);
+    }
+  else
+    {
+      align = GET_MODE_ALIGNMENT (mode);
+      size = GET_MODE_BITSIZE (mode);
+    }
+
+  /* We may need to split multiword moves, so make sure that each word
+     can be accessed without inducing a carry.  */
+  if (size > BITS_PER_WORD
+      && (!TARGET_STRICT_ALIGN || size > align))
+    return false;
+
+  return true;
+}
+
 static bool
 loongarch_valid_index_p (struct loongarch_address_info *info, rtx x,
 			  machine_mode mode, bool strict_p)
@@ -1881,6 +1978,26 @@  loongarch_classify_address (struct loongarch_address_info *info, rtx x,
       info->offset = XEXP (x, 1);
       return (loongarch_valid_base_register_p (info->reg, mode, strict_p)
 	      && loongarch_valid_offset_p (info->offset, mode));
+
+    case LO_SUM:
+      info->type = ADDRESS_LO_SUM;
+      info->reg = XEXP (x, 0);
+      info->offset = XEXP (x, 1);
+      /* We have to trust the creator of the LO_SUM to do something vaguely
+	 sane.  Target-independent code that creates a LO_SUM should also
+	 create and verify the matching HIGH.  Target-independent code that
+	 adds an offset to a LO_SUM must prove that the offset will not
+	 induce a carry.  Failure to do either of these things would be
+	 a bug, and we are not required to check for it here.  The MIPS
+	 backend itself should only create LO_SUMs for valid symbolic
+	 constants, with the high part being either a HIGH or a copy
+	 of _gp. */
+      info->symbol_type
+	= loongarch_classify_symbolic_expression (info->offset);
+      return (loongarch_valid_base_register_p (info->reg, mode, strict_p)
+	      && loongarch_valid_lo_sum_p (info->symbol_type, mode,
+					   info->offset));
+
     default:
       return false;
     }
@@ -1937,14 +2054,13 @@  loongarch_address_insns (rtx x, machine_mode mode, bool might_split_p)
     switch (addr.type)
       {
       case ADDRESS_REG:
-	return factor;
-
       case ADDRESS_REG_REG:
-	return factor;
-
       case ADDRESS_CONST_INT:
 	return factor;
 
+      case ADDRESS_LO_SUM:
+	return factor + 1;
+
       case ADDRESS_SYMBOLIC:
 	return factor * loongarch_symbol_insns (addr.symbol_type, mode);
       }
@@ -1972,7 +2088,8 @@  loongarch_signed_immediate_p (unsigned HOST_WIDE_INT x, int bits,
   return loongarch_unsigned_immediate_p (x, bits, shift);
 }
 
-/* Return true if X is a legitimate address with a 12-bit offset.
+/* Return true if X is a legitimate address with a 12-bit offset
+   or addr.type is ADDRESS_LO_SUM.
    MODE is the mode of the value being accessed.  */
 
 bool
@@ -1981,9 +2098,10 @@  loongarch_12bit_offset_address_p (rtx x, machine_mode mode)
   struct loongarch_address_info addr;
 
   return (loongarch_classify_address (&addr, x, mode, false)
-	  && addr.type == ADDRESS_REG
-	  && CONST_INT_P (addr.offset)
-	  && LARCH_U12BIT_OFFSET_P (INTVAL (addr.offset)));
+	  && ((addr.type == ADDRESS_REG
+	       && CONST_INT_P (addr.offset)
+	       && LARCH_12BIT_OFFSET_P (INTVAL (addr.offset)))
+	      || addr.type == ADDRESS_LO_SUM));
 }
 
 /* Return true if X is a legitimate address with a 14-bit offset shifted 2.
@@ -2001,6 +2119,9 @@  loongarch_14bit_shifted_offset_address_p (rtx x, machine_mode mode)
 	  && LARCH_SHIFT_2_OFFSET_P (INTVAL (addr.offset)));
 }
 
+/* Return true if X is a legitimate address with base and index.
+   MODE is the mode of the value being accessed.  */
+
 bool
 loongarch_base_index_address_p (rtx x, machine_mode mode)
 {
@@ -2022,6 +2143,14 @@  loongarch_const_insns (rtx x)
 
   switch (GET_CODE (x))
     {
+    case HIGH:
+      if (!loongarch_symbolic_constant_p (XEXP (x, 0), &symbol_type)
+	  || !loongarch_split_symbol_type (symbol_type))
+	return 0;
+
+      /* This is simply a PCALAU12I.  */
+      return 1;
+
     case CONST_INT:
       return loongarch_integer_cost (INTVAL (x));
 
@@ -2082,6 +2211,8 @@  loongarch_split_const_insns (rtx x)
   return low + high;
 }
 
+static bool loongarch_split_move_insn_p (rtx dest, rtx src);
+
 /* Return the number of instructions needed to implement INSN,
    given that it loads from or stores to MEM.  */
 
@@ -2199,6 +2330,15 @@  loongarch_unspec_address (rtx address, enum loongarch_symbol_type symbol_type)
   return loongarch_unspec_address_offset (base, offset, symbol_type);
 }
 
+/* Emit an instruction of the form (set TARGET SRC).  */
+
+static rtx
+loongarch_emit_set (rtx target, rtx src)
+{
+  emit_insn (gen_rtx_SET (target, src));
+  return target;
+}
+
 /* If OP is an UNSPEC address, return the address to which it refers,
    otherwise return OP itself.  */
 
@@ -2280,6 +2420,7 @@  loongarch_call_tls_get_addr (rtx sym, enum loongarch_symbol_type type, rtx v0)
 {
   rtx loc, a0;
   rtx_insn *insn;
+  rtx tmp = gen_reg_rtx (Pmode);
 
   a0 = gen_rtx_REG (Pmode, GP_ARG_FIRST);
 
@@ -2290,12 +2431,22 @@  loongarch_call_tls_get_addr (rtx sym, enum loongarch_symbol_type type, rtx v0)
 
   start_sequence ();
 
-  if (type == SYMBOL_TLSLDM)
-    emit_insn (loongarch_got_load_tls_ld (a0, loc));
-  else if (type == SYMBOL_TLSGD)
-    emit_insn (loongarch_got_load_tls_gd (a0, loc));
+  if (TARGET_EXPLICIT_RELOCS)
+    {
+      /* Split tls symbol to high and low.  */
+      rtx high = gen_rtx_HIGH (Pmode, copy_rtx (loc));
+      high = loongarch_force_temporary (tmp, high);
+      emit_insn (gen_tls_low (Pmode, a0, high, loc));
+    }
   else
-    gcc_unreachable ();
+    {
+      if (type == SYMBOL_TLSLDM)
+	emit_insn (loongarch_got_load_tls_ld (a0, loc));
+      else if (type == SYMBOL_TLSGD)
+	emit_insn (loongarch_got_load_tls_gd (a0, loc));
+      else
+	gcc_unreachable ();
+    }
 
   insn = emit_call_insn (gen_call_value_internal (v0, loongarch_tls_symbol,
 						  const0_rtx));
@@ -2315,7 +2466,7 @@  loongarch_call_tls_get_addr (rtx sym, enum loongarch_symbol_type type, rtx v0)
 static rtx
 loongarch_legitimize_tls_address (rtx loc)
 {
-  rtx dest, tp, tmp;
+  rtx dest, tp, tmp, tmp1, tmp2, tmp3;
   enum tls_model model = SYMBOL_REF_TLS_MODEL (loc);
   rtx_insn *insn;
 
@@ -2336,21 +2487,45 @@  loongarch_legitimize_tls_address (rtx loc)
       break;
 
     case TLS_MODEL_INITIAL_EXEC:
-      /* la.tls.ie; tp-relative add  */
-      tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM);
-      tmp = gen_reg_rtx (Pmode);
-      emit_insn (loongarch_got_load_tls_ie (tmp, loc));
-      dest = gen_reg_rtx (Pmode);
-      emit_insn (gen_add3_insn (dest, tmp, tp));
+	{
+	  /* la.tls.ie; tp-relative add.  */
+	  tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM);
+	  tmp1 = gen_reg_rtx (Pmode);
+	  dest = gen_reg_rtx (Pmode);
+	  if (TARGET_EXPLICIT_RELOCS)
+	    {
+	      tmp2 = loongarch_unspec_address (loc, SYMBOL_TLS_IE);
+	      tmp3 = gen_reg_rtx (Pmode);
+	      rtx high = gen_rtx_HIGH (Pmode, copy_rtx (tmp2));
+	      high = loongarch_force_temporary (tmp3, high);
+	      emit_insn (gen_ld_from_got (Pmode, tmp1, high, tmp2));
+	    }
+	  else
+	    emit_insn (loongarch_got_load_tls_ie (tmp1, loc));
+	  emit_insn (gen_add3_insn (dest, tmp1, tp));
+	}
       break;
 
     case TLS_MODEL_LOCAL_EXEC:
-      /* la.tls.le; tp-relative add  */
-      tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM);
-      tmp = gen_reg_rtx (Pmode);
-      emit_insn (loongarch_got_load_tls_le (tmp, loc));
-      dest = gen_reg_rtx (Pmode);
-      emit_insn (gen_add3_insn (dest, tmp, tp));
+	{
+	  /* la.tls.le; tp-relative add.  */
+	  tp = gen_rtx_REG (Pmode, THREAD_POINTER_REGNUM);
+	  tmp1 = gen_reg_rtx (Pmode);
+	  dest = gen_reg_rtx (Pmode);
+
+	  if (TARGET_EXPLICIT_RELOCS)
+	    {
+	      tmp2 = loongarch_unspec_address (loc, SYMBOL_TLS_LE);
+	      tmp3 = gen_reg_rtx (Pmode);
+	      rtx high = gen_rtx_HIGH (Pmode, copy_rtx (tmp2));
+	      high = loongarch_force_temporary (tmp3, high);
+	      emit_insn (gen_ori_l_lo12 (Pmode, tmp1, high, tmp2));
+	    }
+	  else
+	    emit_insn (loongarch_got_load_tls_le (tmp1, loc));
+	  emit_insn (gen_add3_insn (dest, tmp1, tp));
+
+	}
       break;
 
     default:
@@ -2399,6 +2574,68 @@  loongarch_force_address (rtx x, machine_mode mode)
   return x;
 }
 
+/* If MODE is MAX_MACHINE_MODE, ADDR appears as a move operand, otherwise
+   it appears in a MEM of that mode.  Return true if ADDR is a legitimate
+   constant in that context and can be split into high and low parts.
+   If so, and if LOW_OUT is nonnull, emit the high part and store the
+   low part in *LOW_OUT.  Leave *LOW_OUT unchanged otherwise.
+
+   Return false if build with '-mno-explicit-relocs'.
+
+   TEMP is as for loongarch_force_temporary and is used to load the high
+   part into a register.
+
+   When MODE is MAX_MACHINE_MODE, the low part is guaranteed to be
+   a legitimize SET_SRC for an .md pattern, otherwise the low part
+   is guaranteed to be a legitimate address for mode MODE.  */
+
+bool
+loongarch_split_symbol (rtx temp, rtx addr, machine_mode mode, rtx *low_out)
+{
+  enum loongarch_symbol_type symbol_type;
+  rtx high;
+
+  /* If build with '-mno-explicit-relocs', don't split symbol.  */
+  if (!TARGET_EXPLICIT_RELOCS)
+    return false;
+
+  if ((GET_CODE (addr) == HIGH && mode == MAX_MACHINE_MODE)
+      || !loongarch_symbolic_constant_p (addr, &symbol_type)
+      || loongarch_symbol_insns (symbol_type, mode) == 0
+      || !loongarch_split_symbol_type (symbol_type))
+    return false;
+
+  if (temp == NULL)
+    temp = gen_reg_rtx (Pmode);
+
+  /* Get the 12-31 bits of the address.  */
+  high = gen_rtx_HIGH (Pmode, copy_rtx (addr));
+  high = loongarch_force_temporary (temp, high);
+
+  if (low_out)
+    switch (symbol_type)
+      {
+      case SYMBOL_PCREL:
+	*low_out = gen_rtx_LO_SUM (Pmode, high, addr);
+	break;
+
+      case SYMBOL_GOT_DISP:
+	/* SYMBOL_GOT_DISP symbols are loaded from the GOT.  */
+	{
+	  rtx low = gen_rtx_LO_SUM (Pmode, high, addr);
+	  rtx mem = gen_rtx_MEM (Pmode, low);
+	  *low_out = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, mem),
+				     UNSPEC_LOAD_FROM_GOT);
+	  break;
+	}
+
+      default:
+	gcc_unreachable ();
+      }
+
+  return true;
+}
+
 /* This function is used to implement LEGITIMIZE_ADDRESS.  If X can
    be legitimized in a way that the generic machinery might not expect,
    return a new address, otherwise return NULL.  MODE is the mode of
@@ -2414,6 +2651,10 @@  loongarch_legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED,
   if (loongarch_tls_symbol_p (x))
     return loongarch_legitimize_tls_address (x);
 
+  /* See if the address can split into a high part and a LO_SUM.  */
+  if (loongarch_split_symbol (NULL, x, mode, &addr))
+    return loongarch_force_address (addr, mode);
+
   /* Handle BASE + OFFSET using loongarch_add_offset.  */
   loongarch_split_plus (x, &base, &offset);
   if (offset != 0)
@@ -2501,6 +2742,13 @@  loongarch_legitimize_const_move (machine_mode mode, rtx dest, rtx src)
       return;
     }
 
+  /* Split moves of symbolic constants into high and low.  */
+  if (loongarch_split_symbol (dest, src, MAX_MACHINE_MODE, &src))
+    {
+      loongarch_emit_set (dest, src);
+      return;
+    }
+
   /* Generate the appropriate access sequences for TLS symbols.  */
   if (loongarch_tls_symbol_p (src))
     {
@@ -3243,21 +3491,12 @@  loongarch_split_move (rtx dest, rtx src, rtx insn_)
 
 /* Return true if a move from SRC to DEST in INSN should be split.  */
 
-bool
+static bool
 loongarch_split_move_insn_p (rtx dest, rtx src)
 {
   return loongarch_split_move_p (dest, src);
 }
 
-/* Split a move from SRC to DEST in INSN, given that
-   loongarch_split_move_insn_p holds.  */
-
-void
-loongarch_split_move_insn (rtx dest, rtx src, rtx insn)
-{
-  loongarch_split_move (dest, src, insn);
-}
-
 /* Implement TARGET_CONSTANT_ALIGNMENT.  */
 
 static HOST_WIDE_INT
@@ -3371,12 +3610,16 @@  loongarch_output_move (rtx dest, rtx src)
 	    case 2:
 	      return "st.h\t%z1,%0";
 	    case 4:
-	      if (const_arith_operand (offset, Pmode))
+	      /* Matching address type with a 12bit offset and
+		 ADDRESS_LO_SUM.  */
+	      if (const_arith_operand (offset, Pmode)
+		  || GET_CODE (offset) == LO_SUM)
 		return "st.w\t%z1,%0";
 	      else
 		return "stptr.w\t%z1,%0";
 	    case 8:
-	      if (const_arith_operand (offset, Pmode))
+	      if (const_arith_operand (offset, Pmode)
+		  || GET_CODE (offset) == LO_SUM)
 		return "st.d\t%z1,%0";
 	      else
 		return "stptr.d\t%z1,%0";
@@ -3409,12 +3652,16 @@  loongarch_output_move (rtx dest, rtx src)
 	    case 2:
 	      return "ld.hu\t%0,%1";
 	    case 4:
-	      if (const_arith_operand (offset, Pmode))
+	      /* Matching address type with a 12bit offset and
+		 ADDRESS_LO_SUM.  */
+	      if (const_arith_operand (offset, Pmode)
+		  || GET_CODE (offset) == LO_SUM)
 		return "ld.w\t%0,%1";
 	      else
 		return "ldptr.w\t%0,%1";
 	    case 8:
-	      if (const_arith_operand (offset, Pmode))
+	      if (const_arith_operand (offset, Pmode)
+		  || GET_CODE (offset) == LO_SUM)
 		return "ld.d\t%0,%1";
 	      else
 		return "ldptr.d\t%0,%1";
@@ -3423,6 +3670,21 @@  loongarch_output_move (rtx dest, rtx src)
 	    }
 	}
 
+      if (src_code == HIGH)
+	{
+	  rtx offset, x;
+	  split_const (XEXP (src, 0), &x, &offset);
+	  enum loongarch_symbol_type type = SYMBOL_PCREL;
+
+	  if (UNSPEC_ADDRESS_P (x))
+	     type = UNSPEC_ADDRESS_TYPE (x);
+
+	  if (type == SYMBOL_TLS_LE)
+	    return "lu12i.w\t%0,%h1";
+	  else
+	    return "pcalau12i\t%0,%h1";
+	}
+
       if (src_code == CONST_INT)
 	{
 	  if (LU12I_INT (src))
@@ -3436,56 +3698,17 @@  loongarch_output_move (rtx dest, rtx src)
 	  else
 	    gcc_unreachable ();
 	}
+    }
 
-      if (symbolic_operand (src, VOIDmode))
-	{
-	  if ((TARGET_CMODEL_TINY && (!loongarch_global_symbol_p (src)
-				      || loongarch_symbol_binds_local_p (src)))
-	      || (TARGET_CMODEL_TINY_STATIC && !loongarch_weak_symbol_p (src)))
-	    {
-	      /* The symbol must be aligned to 4 byte.  */
-	      unsigned int align;
-
-	      if (LABEL_REF_P (src))
-		align = 32 /* Whatever.  */;
-	      else if (CONSTANT_POOL_ADDRESS_P (src))
-		align = GET_MODE_ALIGNMENT (get_pool_mode (src));
-	      else if (TREE_CONSTANT_POOL_ADDRESS_P (src))
-		{
-		  tree exp = SYMBOL_REF_DECL (src);
-		  align = TYPE_ALIGN (TREE_TYPE (exp));
-		  align = loongarch_constant_alignment (exp, align);
-		}
-	      else if (SYMBOL_REF_DECL (src))
-		align = DECL_ALIGN (SYMBOL_REF_DECL (src));
-	      else if (SYMBOL_REF_HAS_BLOCK_INFO_P (src)
-		       && SYMBOL_REF_BLOCK (src) != NULL)
-		align = SYMBOL_REF_BLOCK (src)->alignment;
-	      else
-		align = BITS_PER_UNIT;
-
-	      if (align % (4 * 8) == 0)
-		return "pcaddi\t%0,%%pcrel(%1)>>2";
-	    }
-	  if (TARGET_CMODEL_TINY
-	      || TARGET_CMODEL_TINY_STATIC
-	      || TARGET_CMODEL_NORMAL
-	      || TARGET_CMODEL_LARGE)
-	    {
-	      if (!loongarch_global_symbol_p (src)
-		  || loongarch_symbol_binds_local_p (src))
-		return "la.local\t%0,%1";
-	      else
-		return "la.global\t%0,%1";
-	    }
-	  if (TARGET_CMODEL_EXTREME)
-	    {
-	      sorry ("Normal symbol loading not implemented in extreme mode.");
-	      gcc_unreachable ();
-	    }
-
-	}
+  if (!TARGET_EXPLICIT_RELOCS
+      && dest_code == REG && symbolic_operand (src, VOIDmode))
+    {
+      if (loongarch_classify_symbol (src) == SYMBOL_PCREL)
+	return "la.local\t%0,%1";
+      else
+	return "la.global\t%0,%1";
     }
+
   if (src_code == REG && FP_REG_P (REGNO (src)))
     {
       if (dest_code == REG && FP_REG_P (REGNO (dest)))
@@ -3503,6 +3726,7 @@  loongarch_output_move (rtx dest, rtx src)
 	  return dbl_p ? "fst.d\t%1,%0" : "fst.s\t%1,%0";
 	}
     }
+
   if (dest_code == REG && FP_REG_P (REGNO (dest)))
     {
       if (src_code == MEM)
@@ -3517,6 +3741,7 @@  loongarch_output_move (rtx dest, rtx src)
 	  return dbl_p ? "fld.d\t%0,%1" : "fld.s\t%0,%1";
 	}
     }
+
   gcc_unreachable ();
 }
 
@@ -4345,29 +4570,75 @@  loongarch_memmodel_needs_release_fence (enum memmodel model)
     }
 }
 
+/* Print symbolic operand OP, which is part of a HIGH or LO_SUM
+   in context CONTEXT.  HI_RELOC indicates a high-part reloc.  */
+
+static void
+loongarch_print_operand_reloc (FILE *file, rtx op, bool hi_reloc)
+{
+  const char *reloc;
+
+  switch (loongarch_classify_symbolic_expression (op))
+    {
+    case SYMBOL_PCREL:
+      reloc = hi_reloc ? "%pc_hi20" : "%pc_lo12";
+      break;
+
+    case SYMBOL_GOT_DISP:
+      reloc = hi_reloc ? "%got_pc_hi20" : "%got_pc_lo12";
+      break;
+
+    case SYMBOL_TLS_IE:
+      reloc = hi_reloc ? "%ie_pc_hi20" : "%ie_pc_lo12";
+      break;
+
+    case SYMBOL_TLS_LE:
+      reloc = hi_reloc ? "%le_hi20" : "%le_lo12";
+      break;
+
+    case SYMBOL_TLSGD:
+      reloc = hi_reloc ? "%gd_pc_hi20" : "%got_pc_lo12";
+      break;
+
+    case SYMBOL_TLSLDM:
+      reloc = hi_reloc ? "%ld_pc_hi20" : "%got_pc_lo12";
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  fprintf (file, "%s(", reloc);
+  output_addr_const (file, loongarch_strip_unspec_address (op));
+  fputc (')', file);
+}
+
 /* Implement TARGET_PRINT_OPERAND.  The LoongArch-specific operand codes are:
 
-   'X'	Print CONST_INT OP in hexadecimal format.
-   'x'	Print the low 16 bits of CONST_INT OP in hexadecimal format.
+   'A'	Print a _DB suffix if the memory model requires a release.
+   'b'	Print the address of a memory operand, without offset.
+   'C'	Print the integer branch condition for comparison OP.
    'd'	Print CONST_INT OP in decimal.
+   'F'	Print the FPU branch condition for comparison OP.
+   'G'	Print a DBAR insn if the memory model requires a release.
+   'H'  Print address 52-61bit relocation associated with OP.
+   'h'  Print the high-part relocation associated with OP.
+   'i'	Print i if the operand is not a register.
+   'L'  Print the low-part relocation associated with OP.
    'm'	Print one less than CONST_INT OP in decimal.
-   'y'	Print exact log2 of CONST_INT OP in decimal.
-   'C'	Print the integer branch condition for comparison OP.
    'N'	Print the inverse of the integer branch condition for comparison OP.
-   'F'	Print the FPU branch condition for comparison OP.
-   'W'	Print the inverse of the FPU branch condition for comparison OP.
    'T'	Print 'f' for (eq:CC ...), 't' for (ne:CC ...),
 	      'z' for (eq:?I ...), 'n' for (ne:?I ...).
    't'	Like 'T', but with the EQ/NE cases reversed
-   'Y'	Print loongarch_fp_conditions[INTVAL (OP)]
-   'Z'	Print OP and a comma for 8CC, otherwise print nothing.
-   'z'	Print $0 if OP is zero, otherwise print OP normally.
-   'b'	Print the address of a memory operand, without offset.
    'V'	Print exact log2 of CONST_INT OP element 0 of a replicated
 	  CONST_VECTOR in decimal.
-   'A'	Print a _DB suffix if the memory model requires a release.
-   'G'	Print a DBAR insn if the memory model requires a release.
-   'i'	Print i if the operand is not a register.  */
+   'W'	Print the inverse of the FPU branch condition for comparison OP.
+   'X'	Print CONST_INT OP in hexadecimal format.
+   'x'	Print the low 16 bits of CONST_INT OP in hexadecimal format.
+   'Y'	Print loongarch_fp_conditions[INTVAL (OP)]
+   'y'	Print exact log2 of CONST_INT OP in decimal.
+   'Z'	Print OP and a comma for 8CC, otherwise print nothing.
+   'z'	Print $0 if OP is zero, otherwise print OP normally.  */
 
 static void
 loongarch_print_operand (FILE *file, rtx op, int letter)
@@ -4385,18 +4656,13 @@  loongarch_print_operand (FILE *file, rtx op, int letter)
 
   switch (letter)
     {
-    case 'X':
-      if (CONST_INT_P (op))
-	fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op));
-      else
-	output_operand_lossage ("invalid use of '%%%c'", letter);
+    case 'A':
+      if (loongarch_memmodel_needs_rel_acq_fence ((enum memmodel) INTVAL (op)))
+       fputs ("_db", file);
       break;
 
-    case 'x':
-      if (CONST_INT_P (op))
-	fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op) & 0xffff);
-      else
-	output_operand_lossage ("invalid use of '%%%c'", letter);
+    case 'C':
+      loongarch_print_int_branch_condition (file, code, letter);
       break;
 
     case 'd':
@@ -4406,6 +4672,34 @@  loongarch_print_operand (FILE *file, rtx op, int letter)
 	output_operand_lossage ("invalid use of '%%%c'", letter);
       break;
 
+    case 'F':
+      loongarch_print_float_branch_condition (file, code, letter);
+      break;
+
+    case 'G':
+      if (loongarch_memmodel_needs_release_fence ((enum memmodel) INTVAL (op)))
+	fputs ("dbar\t0", file);
+      break;
+
+    case 'h':
+      if (code == HIGH)
+	op = XEXP (op, 0);
+      loongarch_print_operand_reloc (file, op, true /* hi_reloc */);
+      break;
+
+    case 'H':
+      loongarch_print_operand_reloc (file, op, true /* hi_reloc */);
+      break;
+
+    case 'i':
+      if (code != REG)
+	fputs ("i", file);
+      break;
+
+    case 'L':
+      loongarch_print_operand_reloc (file, op, false /* lo_reloc */);
+      break;
+
     case 'm':
       if (CONST_INT_P (op))
 	fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (op) - 1);
@@ -4413,17 +4707,21 @@  loongarch_print_operand (FILE *file, rtx op, int letter)
 	output_operand_lossage ("invalid use of '%%%c'", letter);
       break;
 
-    case 'y':
-      if (CONST_INT_P (op))
-	{
-	  int val = exact_log2 (INTVAL (op));
-	  if (val != -1)
-	    fprintf (file, "%d", val);
-	  else
-	    output_operand_lossage ("invalid use of '%%%c'", letter);
-	}
-      else
-	output_operand_lossage ("invalid use of '%%%c'", letter);
+    case 'N':
+      loongarch_print_int_branch_condition (file, reverse_condition (code),
+					    letter);
+      break;
+
+    case 'R':
+      loongarch_print_operand_reloc (file, op, false /* lo_reloc */);
+      break;
+
+    case 't':
+    case 'T':
+      {
+	int truth = (code == NE) == (letter == 'T');
+	fputc ("zfnt"[truth * 2 + FCC_REG_P (REGNO (XEXP (op, 0)))], file);
+      }
       break;
 
     case 'V':
@@ -4441,30 +4739,36 @@  loongarch_print_operand (FILE *file, rtx op, int letter)
 	output_operand_lossage ("invalid use of '%%%c'", letter);
       break;
 
-    case 'C':
-      loongarch_print_int_branch_condition (file, code, letter);
-      break;
-
-    case 'N':
-      loongarch_print_int_branch_condition (file, reverse_condition (code),
-					    letter);
+    case 'W':
+      loongarch_print_float_branch_condition (file, reverse_condition (code),
+					      letter);
       break;
 
-    case 'F':
-      loongarch_print_float_branch_condition (file, code, letter);
+    case 'x':
+      if (CONST_INT_P (op))
+	fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op) & 0xffff);
+      else
+	output_operand_lossage ("invalid use of '%%%c'", letter);
       break;
 
-    case 'W':
-      loongarch_print_float_branch_condition (file, reverse_condition (code),
-					      letter);
+    case 'X':
+      if (CONST_INT_P (op))
+	fprintf (file, HOST_WIDE_INT_PRINT_HEX, INTVAL (op));
+      else
+	output_operand_lossage ("invalid use of '%%%c'", letter);
       break;
 
-    case 'T':
-    case 't':
-      {
-	int truth = (code == NE) == (letter == 'T');
-	fputc ("zfnt"[truth * 2 + FCC_REG_P (REGNO (XEXP (op, 0)))], file);
-      }
+    case 'y':
+      if (CONST_INT_P (op))
+	{
+	  int val = exact_log2 (INTVAL (op));
+	  if (val != -1)
+	    fprintf (file, "%d", val);
+	  else
+	    output_operand_lossage ("invalid use of '%%%c'", letter);
+	}
+      else
+	output_operand_lossage ("invalid use of '%%%c'", letter);
       break;
 
     case 'Y':
@@ -4481,21 +4785,6 @@  loongarch_print_operand (FILE *file, rtx op, int letter)
       fputc (',', file);
       break;
 
-    case 'A':
-      if (loongarch_memmodel_needs_rel_acq_fence ((enum memmodel) INTVAL (op)))
-	fputs ("_db", file);
-      break;
-
-    case 'G':
-      if (loongarch_memmodel_needs_release_fence ((enum memmodel) INTVAL (op)))
-	fputs ("dbar\t0", file);
-      break;
-
-    case 'i':
-      if (code != REG)
-	fputs ("i", file);
-      break;
-
     default:
       switch (code)
 	{
@@ -4555,6 +4844,11 @@  loongarch_print_operand_address (FILE *file, machine_mode /* mode  */, rtx x)
 				reg_names[REGNO (addr.offset)]);
 	return;
 
+      case ADDRESS_LO_SUM:
+	fprintf (file, "%s,", reg_names[REGNO (addr.reg)]);
+	loongarch_print_operand_reloc (file, addr.offset, false /* hi_reloc */);
+	return;
+
       case ADDRESS_CONST_INT:
 	fprintf (file, "%s,", reg_names[GP_REG_FIRST]);
 	output_addr_const (file, x);
@@ -5929,6 +6223,12 @@  loongarch_starting_frame_offset (void)
 #undef TARGET_TRAMPOLINE_INIT
 #define TARGET_TRAMPOLINE_INIT loongarch_trampoline_init
 
+#undef TARGET_MIN_ANCHOR_OFFSET
+#define TARGET_MIN_ANCHOR_OFFSET (-IMM_REACH/2)
+
+#undef TARGET_MAX_ANCHOR_OFFSET
+#define TARGET_MAX_ANCHOR_OFFSET (IMM_REACH/2-1)
+
 #undef TARGET_ATOMIC_ASSIGN_EXPAND_FENV
 #define TARGET_ATOMIC_ASSIGN_EXPAND_FENV loongarch_atomic_assign_expand_fenv
 
diff --git a/gcc/config/loongarch/loongarch.h b/gcc/config/loongarch/loongarch.h
index f9de9a6e4fb..89a5bd728fe 100644
--- a/gcc/config/loongarch/loongarch.h
+++ b/gcc/config/loongarch/loongarch.h
@@ -614,7 +614,7 @@  enum reg_class
 #define LU12I_INT(X) LU12I_OPERAND (INTVAL (X))
 #define LU32I_INT(X) LU32I_OPERAND (INTVAL (X))
 #define LU52I_INT(X) LU52I_OPERAND (INTVAL (X))
-#define LARCH_U12BIT_OFFSET_P(OFFSET) (IN_RANGE (OFFSET, -2048, 2047))
+#define LARCH_12BIT_OFFSET_P(OFFSET) (IN_RANGE (OFFSET, -2048, 2047))
 #define LARCH_9BIT_OFFSET_P(OFFSET) (IN_RANGE (OFFSET, -256, 255))
 #define LARCH_16BIT_OFFSET_P(OFFSET) (IN_RANGE (OFFSET, -32768, 32767))
 #define LARCH_SHIFT_2_OFFSET_P(OFFSET) (((OFFSET) & 0x3) == 0)
diff --git a/gcc/config/loongarch/loongarch.md b/gcc/config/loongarch/loongarch.md
index 5c0445dd879..6b6df22a5f1 100644
--- a/gcc/config/loongarch/loongarch.md
+++ b/gcc/config/loongarch/loongarch.md
@@ -57,6 +57,10 @@  (define_c_enum "unspec" [
   ;; CRC
   UNSPEC_CRC
   UNSPEC_CRCC
+
+  UNSPEC_LOAD_FROM_GOT
+  UNSPEC_ORI_L_LO12
+  UNSPEC_TLS_LOW
 ])
 
 (define_c_enum "unspecv" [
@@ -1743,73 +1747,6 @@  (define_insn "*movdf_softfloat"
   [(set_attr "move_type" "move,load,store")
    (set_attr "mode" "DF")])
 
-
-;; 128-bit integer moves
-
-(define_expand "movti"
-  [(set (match_operand:TI 0)
-	(match_operand:TI 1))]
-  "TARGET_64BIT"
-{
-  if (loongarch_legitimize_move (TImode, operands[0], operands[1]))
-    DONE;
-})
-
-(define_insn "*movti"
-  [(set (match_operand:TI 0 "nonimmediate_operand" "=r,r,r,m")
-	(match_operand:TI 1 "move_operand" "r,i,m,rJ"))]
-  "TARGET_64BIT
-   && (register_operand (operands[0], TImode)
-       || reg_or_0_operand (operands[1], TImode))"
-  { return loongarch_output_move (operands[0], operands[1]); }
-  [(set_attr "move_type" "move,const,load,store")
-   (set (attr "mode")
-    (if_then_else (eq_attr "move_type" "imul")
-		      (const_string "SI")
-		      (const_string "TI")))])
-
-;; 128-bit floating point moves
-
-(define_expand "movtf"
-  [(set (match_operand:TF 0)
-	(match_operand:TF 1))]
-  "TARGET_64BIT"
-{
-  if (loongarch_legitimize_move (TFmode, operands[0], operands[1]))
-    DONE;
-})
-
-;; This pattern handles both hard- and soft-float cases.
-(define_insn "*movtf"
-  [(set (match_operand:TF 0 "nonimmediate_operand" "=r,r,m,f,r,f,m")
-	(match_operand:TF 1 "move_operand" "rG,m,rG,rG,f,m,f"))]
-  "TARGET_64BIT
-   && (register_operand (operands[0], TFmode)
-       || reg_or_0_operand (operands[1], TFmode))"
-  "#"
-  [(set_attr "move_type" "move,load,store,mgtf,mftg,fpload,fpstore")
-   (set_attr "mode" "TF")])
-
-(define_split
-  [(set (match_operand:MOVE64 0 "nonimmediate_operand")
-	(match_operand:MOVE64 1 "move_operand"))]
-  "reload_completed && loongarch_split_move_insn_p (operands[0], operands[1])"
-  [(const_int 0)]
-{
-  loongarch_split_move_insn (operands[0], operands[1], curr_insn);
-  DONE;
-})
-
-(define_split
-  [(set (match_operand:MOVE128 0 "nonimmediate_operand")
-	(match_operand:MOVE128 1 "move_operand"))]
-  "reload_completed && loongarch_split_move_insn_p (operands[0], operands[1])"
-  [(const_int 0)]
-{
-  loongarch_split_move_insn (operands[0], operands[1], curr_insn);
-  DONE;
-})
-
 ;; Emit a doubleword move in which exactly one of the operands is
 ;; a floating-point register.  We can't just emit two normal moves
 ;; because of the constraints imposed by the FPU register model;
@@ -1938,6 +1875,57 @@  (define_insn "lu52i_d"
   [(set_attr "type" "arith")
    (set_attr "mode" "DI")])
 
+;; Instructions for adding the low 12 bits of an address to a register.
+;; Operand 2 is the address: loongarch_print_operand works out which relocation
+;; should be applied.
+
+(define_insn "*low<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r")
+ (lo_sum:P (match_operand:P 1 "register_operand" " r")
+     (match_operand:P 2 "symbolic_operand" "")))]
+  "TARGET_EXPLICIT_RELOCS"
+  "addi.<d>\t%0,%1,%L2"
+  [(set_attr "type" "arith")
+   (set_attr "mode" "<MODE>")])
+
+(define_insn "@tls_low<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r")
+	(unspec:P [(mem:P (lo_sum:P (match_operand:P 1 "register_operand" "r")
+				    (match_operand:P 2 "symbolic_operand" "")))]
+	UNSPEC_TLS_LOW))]
+  "TARGET_EXPLICIT_RELOCS"
+  "addi.<d>\t%0,%1,%L2"
+  [(set_attr "type" "arith")
+   (set_attr "mode" "<MODE>")])
+
+;; Instructions for loading address from GOT entry.
+;; operands[1] is pc plus the high half of the address difference with the got
+;; entry;
+;; operands[2] is low 12 bits for low 12 bit of the address difference with the
+;; got entry.
+;; loongarch_print_operand works out which relocation should be applied.
+
+(define_insn "@ld_from_got<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r")
+	(unspec:P [(mem:P (lo_sum:P
+				(match_operand:P 1 "register_operand" "r")
+				(match_operand:P 2 "symbolic_operand")))]
+	UNSPEC_LOAD_FROM_GOT))]
+  "TARGET_EXPLICIT_RELOCS"
+  "ld.<d>\t%0,%1,%L2"
+  [(set_attr "type" "move")]
+)
+
+(define_insn "@ori_l_lo12<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r")
+	(unspec:P [(match_operand:P 1 "register_operand" "r")
+		    (match_operand:P 2 "symbolic_operand")]
+	UNSPEC_ORI_L_LO12))]
+  ""
+  "ori\t%0,%1,%L2"
+  [(set_attr "type" "move")]
+)
+
 ;; Convert floating-point numbers to integers
 (define_insn "frint_<fmt>"
   [(set (match_operand:ANYF 0 "register_operand" "=f")
@@ -2844,48 +2832,14 @@  (define_expand "sibcall"
 })
 
 (define_insn "sibcall_internal"
-  [(call (mem:SI (match_operand 0 "call_insn_operand" "j,c,a,t,h"))
+  [(call (mem:SI (match_operand 0 "call_insn_operand" "j,c,b"))
 	 (match_operand 1 "" ""))]
   "SIBLING_CALL_P (insn)"
-{
-  switch (which_alternative)
-    {
-    case 0:
-      return "jr\t%0";
-    case 1:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r12,(%%pcrel(%0+0x20000))>>18\n\t"
-	       "jirl\t$r0,$r12,%%pcrel(%0+4)-(%%pcrel(%0+4+0x20000)>>18<<18)";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.local\t$r12,$r13,%0\n\tjr\t$r12";
-      else
-	return "b\t%0";
-    case 2:
-      if (TARGET_CMODEL_TINY_STATIC)
-	return "b\t%0";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r12,$r13,%0\n\tjr\t$r12";
-      else
-	return "la.global\t$r12,%0\n\tjr\t$r12";
-    case 3:
-      if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r12,$r13,%0\n\tjr\t$r12";
-      else
-	return "la.global\t$r12,%0\n\tjr\t$r12";
-    case 4:
-      if (TARGET_CMODEL_NORMAL || TARGET_CMODEL_TINY)
-	return "b\t%%plt(%0)";
-      else if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r12,(%%plt(%0)+0x20000)>>18\n\t"
-	       "jirl\t$r0,$r12,%%plt(%0)+4-((%%plt(%0)+(4+0x20000))>>18<<18)";
-      else
-	/* Cmodel extreme and tiny static not support plt.  */
-	gcc_unreachable ();
-    default:
-      gcc_unreachable ();
-    }
-}
-  [(set_attr "jirl" "indirect,direct,direct,direct,direct")])
+  "@
+   jr\t%0
+   b\t%0
+   b\t%%plt(%0)"
+  [(set_attr "jirl" "indirect,direct,direct")])
 
 (define_expand "sibcall_value"
   [(parallel [(set (match_operand 0 "")
@@ -2920,96 +2874,28 @@  (define_expand "sibcall_value"
 
 (define_insn "sibcall_value_internal"
   [(set (match_operand 0 "register_operand" "")
-	(call (mem:SI (match_operand 1 "call_insn_operand" "j,c,a,t,h"))
+	(call (mem:SI (match_operand 1 "call_insn_operand" "j,c,b"))
 	      (match_operand 2 "" "")))]
   "SIBLING_CALL_P (insn)"
-{
-  switch (which_alternative)
-  {
-    case 0:
-      return "jr\t%1";
-    case 1:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r12,%%pcrel(%1+0x20000)>>18\n\t"
-	       "jirl\t$r0,$r12,%%pcrel(%1+4)-((%%pcrel(%1+4+0x20000))>>18<<18)";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.local\t$r12,$r13,%1\n\tjr\t$r12";
-      else
-	return "b\t%1";
-    case 2:
-      if (TARGET_CMODEL_TINY_STATIC)
-	return "b\t%1";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r12,$r13,%1\n\tjr\t$r12";
-      else
-	return "la.global\t$r12,%1\n\tjr\t$r12";
-    case 3:
-      if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r12,$r13,%1\n\tjr\t$r12";
-      else
-	return "la.global\t$r12,%1\n\tjr\t$r12";
-    case 4:
-      if (TARGET_CMODEL_NORMAL || TARGET_CMODEL_TINY)
-	return " b\t%%plt(%1)";
-      else if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r12,(%%plt(%1)+0x20000)>>18\n\t"
-	       "jirl\t$r0,$r12,%%plt(%1)+4-((%%plt(%1)+(4+0x20000))>>18<<18)";
-      else
-	/* Cmodel extreme and tiny static not support plt.  */
-	gcc_unreachable ();
-    default:
-      gcc_unreachable ();
-  }
-}
-  [(set_attr "jirl" "indirect,direct,direct,direct,direct")])
+  "@
+   jr\t%1
+   b\t%1
+   b\t%%plt(%1)"
+  [(set_attr "jirl" "indirect,direct,direct")])
 
 (define_insn "sibcall_value_multiple_internal"
   [(set (match_operand 0 "register_operand" "")
-	(call (mem:SI (match_operand 1 "call_insn_operand" "j,c,a,t,h"))
+	(call (mem:SI (match_operand 1 "call_insn_operand" "j,c,b"))
 	      (match_operand 2 "" "")))
    (set (match_operand 3 "register_operand" "")
 	(call (mem:SI (match_dup 1))
 	      (match_dup 2)))]
   "SIBLING_CALL_P (insn)"
-{
-  switch (which_alternative)
-  {
-    case 0:
-      return "jr\t%1";
-    case 1:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r12,%%pcrel(%1+0x20000)>>18\n\t"
-	       "jirl\t$r0,$r12,%%pcrel(%1+4)-(%%pcrel(%1+4+0x20000)>>18<<18)";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.local\t$r12,$r13,%1\n\tjr\t$r12";
-      else
-	return "b\t%1";
-    case 2:
-      if (TARGET_CMODEL_TINY_STATIC)
-	return "b\t%1";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r12,$r13,%1\n\tjr\t$r12";
-      else
-	return "la.global\t$r12,%1\n\tjr\t$r12";
-    case 3:
-      if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r12,$r13,%1\n\tjr\t$r12";
-      else
-	return "la.global\t$r12,%1\n\tjr\t$r12";
-    case 4:
-      if (TARGET_CMODEL_NORMAL || TARGET_CMODEL_TINY)
-	return "b\t%%plt(%1)";
-      else if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r12,(%%plt(%1)+0x20000)>>18\n\t"
-	       "jirl\t$r0,$r12,%%plt(%1)+4-((%%plt(%1)+(4+0x20000))>>18<<18)";
-      else
-	/* Cmodel extreme and tiny static not support plt.  */
-	gcc_unreachable ();
-    default:
-      gcc_unreachable ();
-  }
-}
-  [(set_attr "jirl" "indirect,direct,direct,direct,direct")])
+  "@
+   jr\t%1
+   b\t%1
+   b\t%%plt(%1)"
+  [(set_attr "jirl" "indirect,direct,direct")])
 
 (define_expand "call"
   [(parallel [(call (match_operand 0 "")
@@ -3025,50 +2911,15 @@  (define_expand "call"
 })
 
 (define_insn "call_internal"
-  [(call (mem:SI (match_operand 0 "call_insn_operand" "e,c,a,t,h"))
+  [(call (mem:SI (match_operand 0 "call_insn_operand" "e,c,b"))
 	 (match_operand 1 "" ""))
    (clobber (reg:SI RETURN_ADDR_REGNUM))]
   ""
-{
-  switch (which_alternative)
-    {
-    case 0:
-      return "jirl\t$r1,%0,0";
-    case 1:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r1,%%pcrel(%0+0x20000)>>18\n\t"
-	       "jirl\t$r1,$r1,%%pcrel(%0+4)-(%%pcrel(%0+4+0x20000)>>18<<18)";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.local\t$r1,$r12,%0\n\tjirl\t$r1,$r1,0";
-      else
-	return "bl\t%0";
-    case 2:
-      if (TARGET_CMODEL_TINY_STATIC)
-	return "bl\t%0";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r1,$r12,%0\n\tjirl\t$r1,$r1,0";
-      else
-	return "la.global\t$r1,%0\n\tjirl\t$r1,$r1,0";
-    case 3:
-      if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r1,$r12,%0\n\tjirl\t$r1,$r1,0";
-      else
-	return "la.global\t$r1,%0\n\tjirl\t$r1,$r1,0";
-    case 4:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r1,(%%plt(%0)+0x20000)>>18\n\t"
-	       "jirl\t$r1,$r1,%%plt(%0)+4-((%%plt(%0)+(4+0x20000))>>18<<18)";
-      else if (TARGET_CMODEL_NORMAL || TARGET_CMODEL_TINY)
-	return "bl\t%%plt(%0)";
-      else
-	/* Cmodel extreme and tiny static not support plt.  */
-	gcc_unreachable ();
-    default:
-      gcc_unreachable ();
-    }
-}
-  [(set_attr "jirl" "indirect,direct,direct,direct,direct")
-   (set_attr "insn_count" "1,2,3,3,2")])
+  "@
+   jirl\t$r1,%0,0
+   bl\t%0
+   bl\t%%plt(%0)"
+  [(set_attr "jirl" "indirect,direct,direct")])
 
 (define_expand "call_value"
   [(parallel [(set (match_operand 0 "")
@@ -3101,100 +2952,30 @@  (define_expand "call_value"
 
 (define_insn "call_value_internal"
   [(set (match_operand 0 "register_operand" "")
-	(call (mem:SI (match_operand 1 "call_insn_operand" "e,c,a,t,h"))
+	(call (mem:SI (match_operand 1 "call_insn_operand" "e,c,b"))
 	      (match_operand 2 "" "")))
    (clobber (reg:SI RETURN_ADDR_REGNUM))]
   ""
-{
-  switch (which_alternative)
-    {
-    case 0:
-      return "jirl\t$r1,%1,0";
-    case 1:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r1,%%pcrel(%1+0x20000)>>18\n\t"
-	       "jirl\t$r1,$r1,%%pcrel(%1+4)-(%%pcrel(%1+4+0x20000)>>18<<18)";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.local\t$r1,$r12,%1\n\tjirl\t$r1,$r1,0";
-      else
-	return "bl\t%1";
-    case 2:
-      if (TARGET_CMODEL_TINY_STATIC)
-	return "bl\t%1";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r1,$r12,%1\n\tjirl\t$r1,$r1,0";
-      else
-	return "la.global\t$r1,%1\n\tjirl\t$r1,$r1,0";
-    case 3:
-      if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r1,$r12,%1\n\tjirl\t$r1,$r1,0";
-      else
-	return "la.global\t$r1,%1\n\tjirl\t$r1,$r1,0";
-    case 4:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r1,(%%plt(%1)+0x20000)>>18\n\t"
-	       "jirl\t$r1,$r1,%%plt(%1)+4-((%%plt(%1)+(4+0x20000))>>18<<18)";
-      else if (TARGET_CMODEL_NORMAL || TARGET_CMODEL_TINY)
-	return "bl\t%%plt(%1)";
-      else
-	/* Cmodel extreme and tiny static not support plt.  */
-	gcc_unreachable ();
-    default:
-      gcc_unreachable ();
-    }
-}
-  [(set_attr "jirl" "indirect,direct,direct,direct,direct")
-   (set_attr "insn_count" "1,2,3,3,2")])
+  "@
+   jirl\t$r1,%1,0
+   bl\t%1
+   bl\t%%plt(%1)"
+  [(set_attr "jirl" "indirect,direct,direct")])
 
 (define_insn "call_value_multiple_internal"
   [(set (match_operand 0 "register_operand" "")
-	(call (mem:SI (match_operand 1 "call_insn_operand" "e,c,a,t,h"))
+	(call (mem:SI (match_operand 1 "call_insn_operand" "e,c,b"))
 	      (match_operand 2 "" "")))
    (set (match_operand 3 "register_operand" "")
 	(call (mem:SI (match_dup 1))
 	      (match_dup 2)))
    (clobber (reg:SI RETURN_ADDR_REGNUM))]
   ""
-{
-  switch (which_alternative)
-    {
-    case 0:
-      return "jirl\t$r1,%1,0";
-    case 1:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r1,%%pcrel(%1+0x20000)>>18\n\t"
-	       "jirl\t$r1,$r1,%%pcrel(%1+4)-(%%pcrel(%1+4+0x20000)>>18<<18)";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.local\t$r1,$r12,%1\n\tjirl\t$r1,$r1,0";
-      else
-	return "bl\t%1";
-    case 2:
-      if (TARGET_CMODEL_TINY_STATIC)
-	return "bl\t%1";
-      else if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r1,$r12,%1\n\tjirl\t$r1,$r1,0 ";
-      else
-	return "la.global\t$r1,%1\n\tjirl\t$r1,$r1,0";
-    case 3:
-      if (TARGET_CMODEL_EXTREME)
-	return "la.global\t$r1,$r12,%1\n\tjirl\t$r1,$r1,0";
-      else
-	return "la.global\t$r1,%1\n\tjirl\t$r1,$r1,0";
-    case 4:
-      if (TARGET_CMODEL_LARGE)
-	return "pcaddu18i\t$r1,(%%plt(%1)+0x20000)>>18\n\t"
-	       "jirl\t$r1,$r1,%%plt(%1)+4-((%%plt(%1)+(4+0x20000))>>18<<18)";
-      else if (TARGET_CMODEL_NORMAL || TARGET_CMODEL_TINY)
-	return "bl\t%%plt(%1)";
-      else
-	/* Cmodel extreme and tiny static not support plt.  */
-	gcc_unreachable ();
-    default:
-      gcc_unreachable ();
-    }
-}
-  [(set_attr "jirl" "indirect,direct,direct,direct,direct")
-   (set_attr "insn_count" "1,2,3,3,2")])
+  "@
+   jirl\t$r1,%1,0
+   bl\t%1
+   bl\t%%plt(%1)"
+  [(set_attr "jirl" "indirect,direct,direct")])
 
 
 ;; Call subroutine returning any type.
diff --git a/gcc/config/loongarch/loongarch.opt b/gcc/config/loongarch/loongarch.opt
index 3ff0d860413..7a8c5b44418 100644
--- a/gcc/config/loongarch/loongarch.opt
+++ b/gcc/config/loongarch/loongarch.opt
@@ -161,6 +161,10 @@  mmax-inline-memcpy-size=
 Target Joined RejectNegative UInteger Var(loongarch_max_inline_memcpy_size) Init(1024)
 -mmax-inline-memcpy-size=SIZE	Set the max size of memcpy to inline, default is 1024.
 
+mexplicit-relocs
+Target Var(TARGET_EXPLICIT_RELOCS) Init(1)
+Use %reloc() assembly operators.
+
 ; The code model option names for -mcmodel.
 Enum
 Name(cmodel) Type(int)
diff --git a/gcc/config/loongarch/predicates.md b/gcc/config/loongarch/predicates.md
index edd74d4783d..cd3528c7c97 100644
--- a/gcc/config/loongarch/predicates.md
+++ b/gcc/config/loongarch/predicates.md
@@ -110,21 +110,30 @@  (define_predicate "low_bitmask_operand"
 (define_predicate "const_call_insn_operand"
   (match_code "const,symbol_ref,label_ref")
 {
+  /* Split symbol to high and low if return false.
+     If defined TARGET_CMODEL_LARGE, all symbol would be splited,
+     else if offset is not zero, the symbol would be splited.  */
+
   enum loongarch_symbol_type symbol_type;
+  loongarch_symbolic_constant_p (op, &symbol_type);
+
+  rtx offset, x = op;
+  split_const (x, &x, &offset);
 
-  if (!loongarch_symbolic_constant_p (op, &symbol_type))
+  if (offset != const0_rtx)
     return false;
 
   switch (symbol_type)
     {
-    case SYMBOL_GOT_DISP:
-      /* Without explicit relocs, there is no special syntax for
-	 loading the address of a call destination into a register.
-	 Using "la.global JIRL_REGS,foo; jirl JIRL_REGS" would prevent the lazy
-	 binding of "foo", so keep the address of global symbols with the jirl
-	 macro.  */
+    case SYMBOL_PCREL:
       return 1;
 
+    case SYMBOL_GOT_DISP:
+      if (TARGET_CMODEL_LARGE || !flag_plt)
+	return false;
+      else
+	return 1;
+
     default:
       return false;
     }
@@ -140,22 +149,11 @@  (define_predicate "is_const_call_local_symbol"
 	    (match_test "loongarch_symbol_binds_local_p (op) != 0"))
        (match_test "CONSTANT_P (op)")))
 
-(define_predicate "is_const_call_weak_symbol"
-  (and (match_operand 0 "const_call_insn_operand")
-       (not (match_operand 0 "is_const_call_local_symbol"))
-       (match_test "loongarch_weak_symbol_p (op) != 0")
-       (match_test "CONSTANT_P (op)")))
-
-(define_predicate "is_const_call_plt_symbol"
-  (and (match_operand 0 "const_call_insn_operand")
-       (match_test "flag_plt != 0")
-       (match_test "loongarch_global_symbol_noweak_p (op) != 0")
-       (match_test "CONSTANT_P (op)")))
-
-(define_predicate "is_const_call_global_noplt_symbol"
+(define_predicate "is_const_call_no_local_symbol"
   (and (match_operand 0 "const_call_insn_operand")
-       (match_test "flag_plt == 0")
-       (match_test "loongarch_global_symbol_noweak_p (op) != 0")
+       (ior (match_test "loongarch_global_symbol_p (op) != 0")
+	    (match_test "loongarch_symbol_binds_local_p (op) == 0")
+       (match_test "loongarch_weak_symbol_p (op) != 0"))
        (match_test "CONSTANT_P (op)")))
 
 ;; A legitimate CONST_INT operand that takes more than one instruction
@@ -219,7 +217,19 @@  (define_predicate "move_operand"
     case CONST:
     case SYMBOL_REF:
     case LABEL_REF:
-      return (loongarch_symbolic_constant_p (op, &symbol_type));
+      return (loongarch_symbolic_constant_p (op, &symbol_type)
+	      && (!TARGET_EXPLICIT_RELOCS
+		  || !loongarch_split_symbol_type (symbol_type)));
+
+    case HIGH:
+      /* '-mno-explicit-relocs' don't generate high/low pairs.  */
+      if (!TARGET_EXPLICIT_RELOCS)
+	return false;
+
+      op = XEXP (op, 0);
+      return (loongarch_symbolic_constant_p (op, &symbol_type)
+	      && loongarch_split_symbol_type (symbol_type));
+
     default:
       return true;
     }