From 62fdbd084f7ab04cb7a97d6186ffe70acfc70f1b Mon Sep 17 00:00:00 2001
From: Xi Ruoyao <xry111@xry111.site>
Date: Tue, 28 Apr 2026 20:32:38 +0800
Subject: [PATCH] LoongArch: harden SSP canary set and test routines [PR
 125049]

Add the stack_protect_combined_{set,test} expanders to expand the
routines as unsplitable insns which does not leave any sensitive data
(the canary value, the canary address, and all the intermediate values
used materializing the address) in a register.  This prevents the
attacker from defeating SSP by probing the canary value from the
register context or overwriting the address spilled onto the stack.

	PR target/125049

gcc/

	* config/loongarch/predicates.md (ssp_operand): New
	define_predicate.
	(ssp_normal_operand): New define_predicate.
	* config/loongarch/constraints.md (ZE): New define_constraint.
	(ZF): New define_constraint.
	* config/loongarch/loongarch.md (UNSPEC_SSP): New unspec.
	(cbranch4): Add "@" to create gen_cbranch4(machine_mode, ...).
	(@stack_protect_combined_set_normal_<mode>): New define_insn.
	(@stack_protect_combined_set_extreme_<mode>): New define_insn.
	(@stack_protect_combined_test_internal_<mode>): New define_insn.
	(stack_protect_combined_set): New define_expand.
	(stack_protect_combined_test): New define_expand.
	* config/loongarch/loongarch-protos.h
	(loongarch_output_asm_load_canary): Declare.
	* config/loongarch/loongarch.cc (loongarch_print_operand): Allow
	'v' to print d/w for DImode/SImode.
	(loongarch_output_asm_load_canary): Implement.

gcc/testsuite/

	* gcc.target/loongarch/pr125049.c: New test.
---
 gcc/config/loongarch/constraints.md           |  9 ++
 gcc/config/loongarch/loongarch-protos.h       |  1 +
 gcc/config/loongarch/loongarch.cc             | 51 +++++++++++
 gcc/config/loongarch/loongarch.md             | 88 ++++++++++++++++++-
 gcc/config/loongarch/predicates.md            |  8 ++
 gcc/testsuite/gcc.target/loongarch/pr125049.c | 50 +++++++++++
 6 files changed, 206 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.target/loongarch/pr125049.c

diff --git a/gcc/config/loongarch/constraints.md b/gcc/config/loongarch/constraints.md
index 2b10d685137..b681cf95205 100644
--- a/gcc/config/loongarch/constraints.md
+++ b/gcc/config/loongarch/constraints.md
@@ -370,3 +370,12 @@ (define_address_constraint "ZD"
    and offset that is suitable for use in instructions with the same
    addressing mode as @code{preld}."
    (match_test "loongarch_12bit_offset_address_p (op, mode)"))
+
+(define_constraint "ZE"
+  "A symbolic suitable as stack canary in the normal/medium code model."
+  (match_operand 0 "ssp_normal_operand"))
+
+(define_constraint "ZF"
+  "A symbolic suitable as stack canary, but in the extreme code model."
+  (and (match_operand 0 "ssp_operand")
+       (not (match_operand 0 "ssp_normal_operand"))))
diff --git a/gcc/config/loongarch/loongarch-protos.h b/gcc/config/loongarch/loongarch-protos.h
index 11575a15454..e67addf36e3 100644
--- a/gcc/config/loongarch/loongarch-protos.h
+++ b/gcc/config/loongarch/loongarch-protos.h
@@ -235,4 +235,5 @@ extern bool loongarch_parse_fmv_features (location_t, string_slice,
 extern void get_feature_mask_for_version (tree, loongarch_fmv_feature_mask *,
 					  auto_vec<unsigned int> *);
 extern int loongarch_compare_version_priority (tree, tree);
+extern void loongarch_output_asm_load_canary (rtx reg, rtx canary, rtx tmp);
 #endif /* ! GCC_LOONGARCH_PROTOS_H */
diff --git a/gcc/config/loongarch/loongarch.cc b/gcc/config/loongarch/loongarch.cc
index 1ecf356ac6c..fd3791e2455 100644
--- a/gcc/config/loongarch/loongarch.cc
+++ b/gcc/config/loongarch/loongarch.cc
@@ -6969,12 +6969,14 @@ loongarch_print_operand (FILE *file, rtx op, int letter)
 	case E_V4SFmode:
 	case E_V8SImode:
 	case E_V8SFmode:
+	case E_SImode:
 	  fprintf (file, "w");
 	  break;
 	case E_V2DImode:
 	case E_V2DFmode:
 	case E_V4DImode:
 	case E_V4DFmode:
+	case E_DImode:
 	  fprintf (file, "d");
 	  break;
 	default:
@@ -12144,6 +12146,55 @@ loongarch_option_same_function_versions (string_slice str1, const_tree,
   return feature_mask1 == feature_mask2;
 }
 
+/* Output assembly to materialize the address of the stack canary value
+   into reg.  The third argument, tmp, should be and should only be
+   non-NULL if the extreme code model is effective for the canary.  If
+   the fourth arugment, load, is true, the canary value is loaded into
+   the register.
+
+   The assembly cannot be splitted due to security reason.  */
+void
+loongarch_output_asm_load_canary (rtx reg, rtx canary, rtx tmp)
+{
+  gcc_checking_assert (ssp_operand (canary, VOIDmode));
+  gcc_checking_assert ((!tmp) == ssp_normal_operand (canary, VOIDmode));
+  gcc_checking_assert (register_operand (reg, Pmode));
+
+  rtx op[] = {reg, canary, tmp};
+  bool got = (loongarch_classify_symbol (canary) == SYMBOL_GOT_DISP);
+  bool need_ld = false;
+
+  if (la_opt_explicit_relocs != EXPLICIT_RELOCS_ALWAYS)
+    {
+      if (got)
+	output_asm_insn (tmp ? "la.global\t%0,%2,%1" : "la.global\t%0,%1",
+			 op);
+      else
+	output_asm_insn (tmp ? "la.local\t%0,%2,%1" : "la.local\t%0,%1",
+			 op);
+
+      need_ld = true;
+    }
+  else
+    {
+      output_asm_insn ("pcalau12i\t%0,%r1", op);
+      if (!tmp)
+	output_asm_insn ("ld.%v0\t%0,%0,%L1", op);
+      else
+	{
+	  output_asm_insn ("addi.d\t%2,$r0,%L1", op);
+	  output_asm_insn ("lu32i.d\t%2,%R1", op);
+	  output_asm_insn ("lu52i.d\t%2,%2,%H1", op);
+	  output_asm_insn ("ldx.d\t%0,%0,%2", op);
+	}
+
+      need_ld = got;
+    }
+
+  if (need_ld)
+    output_asm_insn ("ld.%v0\t%0,%0,0", op);
+}
+
 /* Initialize the GCC target structure.  */
 #undef TARGET_ASM_ALIGNED_HI_OP
 #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t"
diff --git a/gcc/config/loongarch/loongarch.md b/gcc/config/loongarch/loongarch.md
index f8693b7f593..5edba0d511a 100644
--- a/gcc/config/loongarch/loongarch.md
+++ b/gcc/config/loongarch/loongarch.md
@@ -83,6 +83,8 @@ (define_c_enum "unspec" [
   UNSPEC_LOAD_SYMBOL_OFFSET64
   UNSPEC_LA_PCREL_64_PART1
   UNSPEC_LA_PCREL_64_PART2
+
+  UNSPEC_SSP
 ])
 
 (define_c_enum "unspecv" [
@@ -3686,7 +3688,7 @@ (define_insn "*branch_equality<mode>_inverted"
 ;; QImode values so we can force zero-extension.
 (define_mode_iterator BR [(QI "TARGET_64BIT") SI (DI "TARGET_64BIT")])
 
-(define_expand "cbranch<mode>4"
+(define_expand "@cbranch<mode>4"
   [(set (pc)
 	(if_then_else (match_operator 0 "comparison_operator"
 			[(match_operand:BR 1 "register_operand")
@@ -4995,6 +4997,90 @@ (define_insn_and_rewrite "simple_store<mode>"
     operands[0] = loongarch_rewrite_mem_for_simple_ldst (operands[0]);
   })
 
+;; Set and check against stack canary without leaving it in a register.
+;; DO NOT ATTEMPT TO SPLIT THESE INSNS!  It's important for security reason
+;; that the canary value does not live beyond the life of this sequence.
+
+(define_insn "@stack_protect_combined_set_normal_<mode>"
+  [(set (match_operand:P 0 "memory_operand" "=m,ZC")
+        (unspec:P [(mem:P (match_operand:P 1 "ssp_normal_operand"))]
+		  UNSPEC_SSP))
+   (set (match_scratch:P 2 "=&r,&r") (const_int 0))]
+  ""
+{
+  loongarch_output_asm_load_canary (operands[2], operands[1], NULL_RTX);
+  output_asm_insn (which_alternative ? "stptr.d\t%2,%0" : "st.d\t%2,%0",
+		   operands);
+  return "ori\t%2,$r0,0";
+}
+  [(set_attr "type" "store")
+   (set_attr "length" "20")])
+
+(define_insn "@stack_protect_combined_set_extreme_<mode>"
+  [(set (match_operand:P 0 "memory_operand" "=m,ZC")
+        (unspec:P [(mem:P (match_operand:P 1 "ssp_operand"))] UNSPEC_SSP))
+   (set (match_scratch:P 2 "=&r,&r") (const_int 0))
+   (set (match_scratch:P 3 "=&r,&r") (const_int 0))]
+  ""
+{
+  loongarch_output_asm_load_canary (operands[2], operands[1], operands[3]);
+  output_asm_insn (which_alternative ? "stptr.d\t%2,%0" : "st.d\t%2,%0",
+		   operands);
+  return "ori\t%2,$r0,0\n\tori\t%3,$r0,0";
+}
+  [(set_attr "type" "store")
+   (set_attr "length" "36")])
+
+(define_insn "@stack_protect_combined_test_internal_<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r,r,&r,&r")
+	(xor:P
+	  (match_operand:P 1 "memory_operand" "=m,ZC,m,ZC")
+	    (unspec:P
+	      [(mem:P (match_operand:P 2 "ssp_operand" "ZE,ZE,ZF,ZF"))]
+	      UNSPEC_SSP)))
+   (set (match_scratch:P 3 "=&r,&r,&r,&r") (const_int 0))]
+  ""
+{
+  rtx t = (which_alternative >= 2 ? operands[0] : NULL_RTX);
+  loongarch_output_asm_load_canary (operands[3], operands[2], t);
+  output_asm_insn ((which_alternative & 1) ? "ldptr.d\t%0,%1"
+					   : "ld.d\t%0,%1",
+		   operands);
+  return "xor\t%0,%0,%3\n\tori\t%3,$r0,0";
+}
+  [(set_attr "type" "load,load,load,load")
+   (set_attr "length" "24,24,36,36")])
+
+(define_expand "stack_protect_combined_set"
+  [(match_operand 0 "memory_operand")
+   (match_operand 1 "memory_operand")]
+  ""
+{
+  rtx canary = XEXP (operands[1], 0);
+  auto fn = (ssp_normal_operand (canary, VOIDmode)
+	     ? gen_stack_protect_combined_set_normal
+	     : gen_stack_protect_combined_set_extreme);
+
+  emit_insn (fn (Pmode, operands[0], canary));
+  DONE;
+})
+
+(define_expand "stack_protect_combined_test"
+  [(match_operand 0 "memory_operand")
+   (match_operand 1 "memory_operand")
+   (match_operand 2 "")]
+  ""
+{
+  rtx t = gen_reg_rtx (Pmode);
+  rtx canary = XEXP (operands[1], 0);
+  emit_insn (gen_stack_protect_combined_test_internal (Pmode, t,
+						       operands[0],
+						       canary));
+  rtx cond = gen_rtx_EQ (VOIDmode, t, const0_rtx);
+  emit_jump_insn (gen_cbranch4 (Pmode, cond, t, const0_rtx, operands[2]));
+  DONE;
+})
+
 ;; Synchronization instructions.
 
 (include "sync.md")
diff --git a/gcc/config/loongarch/predicates.md b/gcc/config/loongarch/predicates.md
index da46de8ec04..af3b770c4e9 100644
--- a/gcc/config/loongarch/predicates.md
+++ b/gcc/config/loongarch/predicates.md
@@ -610,6 +610,14 @@ (define_predicate "symbolic_off64_or_reg_operand"
  (ior (match_operand 0 "register_operand")
       (match_operand 0 "symbolic_off64_operand")))
 
+;; Currently stack canary must be the global symbol __stack_chk_guard.
+(define_predicate "ssp_operand" (match_code "symbol_ref"))
+
+;; If the stack canary is within the normal/medium code model.
+(define_predicate "ssp_normal_operand"
+  (and (match_operand 0 "ssp_operand")
+       (not (match_operand 0 "symbolic_off64_operand"))))
+
 (define_predicate "equality_operator"
   (match_code "eq,ne"))
 
diff --git a/gcc/testsuite/gcc.target/loongarch/pr125049.c b/gcc/testsuite/gcc.target/loongarch/pr125049.c
new file mode 100644
index 00000000000..cfe036e2061
--- /dev/null
+++ b/gcc/testsuite/gcc.target/loongarch/pr125049.c
@@ -0,0 +1,50 @@
+/* PR 125049: ensure stack canary and its address are not leaked.  */
+/* { dg-options "-O2 -fstack-protector-strong -ffixed-r30 -ffixed-r31" } */
+/* { dg-do run } */
+/* { dg-require-effective-target fstack_protector } */
+
+extern long __stack_chk_guard;
+register long s7 asm ("s7"), *s8 asm ("s8");
+
+[[gnu::zero_call_used_regs ("all"), gnu::noipa]] void
+init_test (void)
+{
+  s7 = __stack_chk_guard;
+  s8 = &__stack_chk_guard;
+}
+
+[[gnu::always_inline]] static inline void
+check_reg (void)
+{
+#pragma GCC unroll 30
+  for (int i = 4; i < 30; i++)
+    asm goto (
+      "beq $r%0,$s7,%l[error]\n\t"
+      "beq $r%0,$s8,%l[error]\n\t"
+      :
+      : "i" (i)
+      :
+      : error
+    );
+  return;
+error:
+  __builtin_trap ();
+}
+
+[[gnu::noipa]] void
+test (void)
+{
+  char buf[256];
+  asm ("":"+m"(buf));
+
+  check_reg ();
+}
+
+int
+main (void)
+{
+  init_test ();
+  test ();
+
+  check_reg ();
+}
-- 
2.54.0

From 25fe4e719d0689ab7e38ddaed02c90d1b70ef138 Mon Sep 17 00:00:00 2001
From: Xi Ruoyao <xry111@xry111.site>
Date: Wed, 13 May 2026 00:22:15 +0800
Subject: [PATCH] scev: Sign extend step in peeled converted IV handling [PR
 125291]

For 3 iterations of

    unsigned char flagbits;
    _877 = flagbits_832 + 254;
    _879 = (int) _877;
    # prephitmp_880 = PHI <_879(40), 6(41)>
    _70 = _68 >> prephitmp_880;

The peeled converted IV handling added in r16-3562 incorrectly analyzes
it as [6, 6 + 254, 6 + 254 * 2] instead of [6, 4, 2].  Then VRP uses the
intersect of {6, 560, 514} and {2, 4, 6}, i.e. {6} as the possible value
range, and propagates the constant 6 for _70.

Extend the step (for example, 254 => -2) to fix the issue.

	PR tree-optimization/125291

gcc/

	* tree-scalar-evolution.cc (simplify_peeled_chrec): Sign-extend
	the step for peeled converted IV.

gcc/testsuite/

	* gcc.c-torture/execute/pr125291.c: New test.

(cherry picked from commit 26aae2119eb79bfbea23b86ea1a58f9a3a0911a6)
---
 .../gcc.c-torture/execute/pr125291.c          | 39 +++++++++++++++++++
 gcc/tree-scalar-evolution.cc                  | 17 ++++++--
 2 files changed, 52 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.c-torture/execute/pr125291.c

diff --git a/gcc/testsuite/gcc.c-torture/execute/pr125291.c b/gcc/testsuite/gcc.c-torture/execute/pr125291.c
new file mode 100644
index 00000000000..073866a9b73
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/execute/pr125291.c
@@ -0,0 +1,39 @@
+/* PR tree-optimization/125291 */
+
+char buf[1111];
+char *archive_le16dec_filename = buf;
+unsigned int archive_le16dec_end, archive_le16dec_fn_end,
+    archive_le16dec_filename_size, archive_le16dec_offset;
+char archive_le16dec_p[] = { 21, 0x7f };
+
+[[gnu::noipa]]
+void
+archive_le16dec ()
+{
+  archive_le16dec_filename_size = (short)archive_le16dec_filename_size;
+  unsigned char flagbits = 0, flagbyte;
+  archive_le16dec_end = archive_le16dec_filename_size;
+  archive_le16dec_fn_end = archive_le16dec_filename_size * 2;
+  archive_le16dec_filename_size = flagbits = 0;
+  while (archive_le16dec_offset < archive_le16dec_end
+         && archive_le16dec_filename_size < archive_le16dec_fn_end)
+    {
+      if (!flagbits)
+        {
+          flagbyte = archive_le16dec_p[archive_le16dec_offset++];
+          flagbits = 8;
+        }
+      flagbits -= 2;
+      if (!(flagbyte >> flagbits & 3))
+        archive_le16dec_filename_size++;
+    }
+}
+
+int
+main ()
+{
+  archive_le16dec_filename_size = 2;
+  archive_le16dec ();
+  if (archive_le16dec_filename_size != 1)
+    __builtin_trap ();
+}
diff --git a/gcc/tree-scalar-evolution.cc b/gcc/tree-scalar-evolution.cc
index e05a171c1b1..5076ce446c1 100644
--- a/gcc/tree-scalar-evolution.cc
+++ b/gcc/tree-scalar-evolution.cc
@@ -1391,10 +1391,19 @@ simplify_peeled_chrec (class loop *loop, tree arg, tree init_cond)
 	  && wi::to_widest (init_cond) == wi::to_widest (left_before)
 	  && !scev_probably_wraps_p (NULL_TREE, left_before, right, NULL,
 				     loop, false))
-	return build_polynomial_chrec (loop->num, init_cond,
-				       chrec_convert (TREE_TYPE (ev),
-						      right, NULL,
-						      false, NULL_TREE));
+	{
+	  tree tp = TREE_TYPE (right);
+
+	  /* We need a sign-extension to make things like
+	     u8(6, 4, 2) => i32(6, 4, 2), instead of i32(6, 260, 514).  */
+	  if (TYPE_UNSIGNED (tp))
+	    right = fold_convert (signed_type_for (tp), right);
+
+	  return build_polynomial_chrec (loop->num, init_cond,
+					 chrec_convert (TREE_TYPE (ev),
+							right, NULL,
+							false, NULL_TREE));
+	}
       return chrec_dont_know;
     }
 
-- 
2.54.0

