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;
     }
