diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index a87bcb298f9..2889a471fa5 100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -302,6 +302,7 @@ static bool arm_vectorize_vec_perm_const (machine_mode, rtx, rtx, rtx,
 					  const vec_perm_indices &);
 
 static bool aarch_macro_fusion_pair_p (rtx_insn*, rtx_insn*);
+static bool arm_pac_enabled_for_curr_function_p (void);
 
 static int arm_builtin_vectorization_cost (enum vect_cost_for_stmt type_of_cost,
 					   tree vectype,
@@ -22696,6 +22697,10 @@ arm_compute_frame_layout (void)
 	 nonecure entry functions with VSTR/VLDR.  */
       if (TARGET_HAVE_FPCXT_CMSE && IS_CMSE_ENTRY (func_type))
 	saved += 4;
+
+      /* Allocate space for saving R12 */
+      if (arm_pac_enabled_for_curr_function_p ())
+	saved += 4;
     }
   else /* TARGET_THUMB1 */
     {
@@ -23288,11 +23293,12 @@ arm_expand_prologue (void)
   /* The static chain register is the same as the IP register.  If it is
      clobbered when creating the frame, we need to save and restore it.  */
   clobber_ip = IS_NESTED (func_type)
-	       && ((TARGET_APCS_FRAME && frame_pointer_needed && TARGET_ARM)
-		   || ((flag_stack_check == STATIC_BUILTIN_STACK_CHECK
-			|| flag_stack_clash_protection)
-		       && !df_regs_ever_live_p (LR_REGNUM)
-		       && arm_r3_live_at_start_p ()));
+    && (((TARGET_APCS_FRAME && frame_pointer_needed && TARGET_ARM)
+	 || ((flag_stack_check == STATIC_BUILTIN_STACK_CHECK
+	      || flag_stack_clash_protection)
+	     && !df_regs_ever_live_p (LR_REGNUM)
+	     && arm_r3_live_at_start_p ()))
+	|| (arm_pac_enabled_for_curr_function_p ()));
 
   /* Find somewhere to store IP whilst the frame is being created.
      We try the following places in order:
@@ -23368,6 +23374,14 @@ arm_expand_prologue (void)
 	}
     }
 
+  if (arm_pac_enabled_for_curr_function_p ())
+    {
+      if (aarch_bti_enabled ())
+	emit_insn (gen_pacbti_ip_lr_sp ());
+      else
+	emit_insn (gen_pac_ip_lr_sp ());
+    }
+
   if (TARGET_APCS_FRAME && frame_pointer_needed && TARGET_ARM)
     {
       if (IS_INTERRUPT (func_type))
@@ -23490,6 +23504,9 @@ arm_expand_prologue (void)
   if (! IS_VOLATILE (func_type))
     saved_regs += arm_save_coproc_regs ();
 
+  if (arm_pac_enabled_for_curr_function_p ())
+      emit_multi_reg_push (1 << IP_REGNUM, 1 << IP_REGNUM);
+
   if (frame_pointer_needed && TARGET_ARM)
     {
       /* Create the new frame pointer.  */
@@ -27150,7 +27167,8 @@ thumb2_expand_return (bool simple_return)
 	 to assert it for now to ensure that future code changes do not silently
 	 change this behavior.  */
       gcc_assert (!IS_CMSE_ENTRY (arm_current_func_type ()));
-      if (num_regs == 1)
+      if (num_regs == 1
+	  && !(arm_pac_enabled_for_curr_function_p ()))
         {
           rtx par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (2));
           rtx reg = gen_rtx_REG (SImode, PC_REGNUM);
@@ -27165,13 +27183,34 @@ thumb2_expand_return (bool simple_return)
         }
       else
         {
-          saved_regs_mask &= ~ (1 << LR_REGNUM);
-          saved_regs_mask |=   (1 << PC_REGNUM);
-          arm_emit_multi_reg_pop (saved_regs_mask);
+	  if (arm_pac_enabled_for_curr_function_p ())
+	    {
+	      emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx,
+				     GEN_INT (4)));
+	      arm_emit_multi_reg_pop (1 << IP_REGNUM);
+	      saved_regs_mask &= ~ (1 << PC_REGNUM);
+	      arm_emit_multi_reg_pop (saved_regs_mask);
+	      emit_insn (gen_aut_ip_lr_sp ());
+	      emit_jump_insn (simple_return_rtx);
+	    }
+	  else
+	    {
+	      saved_regs_mask &= ~ (1 << LR_REGNUM);
+	      saved_regs_mask |=   (1 << PC_REGNUM);
+	      arm_emit_multi_reg_pop (saved_regs_mask);
+	    }
         }
     }
   else
     {
+      if (arm_pac_enabled_for_curr_function_p ())
+	{
+	  emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx,
+				 GEN_INT (4)));
+	  arm_emit_multi_reg_pop (1 << IP_REGNUM);
+	  emit_insn (gen_aut_ip_lr_sp ());
+	}
+
       if (IS_CMSE_ENTRY (arm_current_func_type ()))
 	cmse_nonsecure_entry_clear_before_return ();
       emit_jump_insn (simple_return_rtx);
@@ -27469,6 +27508,9 @@ arm_expand_epilogue (bool really_return)
           /* In Thumb-2 mode, the frame pointer points to the last saved
              register.  */
 	  amount = offsets->locals_base - offsets->saved_regs;
+	  if (arm_pac_enabled_for_curr_function_p ())
+	    amount += 4;
+
 	  if (amount)
 	    {
 	      insn = emit_insn (gen_addsi3 (hard_frame_pointer_rtx,
@@ -27497,6 +27539,10 @@ arm_expand_epilogue (bool really_return)
       /* Pop off outgoing args and local frame to adjust stack pointer to
          last saved register.  */
       amount = offsets->outgoing_args - offsets->saved_regs;
+
+      if (arm_pac_enabled_for_curr_function_p ())
+	amount += 4;
+
       if (amount)
         {
 	  rtx_insn *tmp;
@@ -27562,6 +27608,9 @@ arm_expand_epilogue (bool really_return)
 				       stack_pointer_rtx, stack_pointer_rtx);
         }
 
+  if (arm_pac_enabled_for_curr_function_p ())
+    arm_emit_multi_reg_pop (1 << IP_REGNUM);
+
   if (saved_regs_mask)
     {
       rtx insn;
@@ -27574,7 +27623,8 @@ arm_expand_epilogue (bool really_return)
           && really_return
           && crtl->args.pretend_args_size == 0
           && saved_regs_mask & (1 << LR_REGNUM)
-          && !crtl->calls_eh_return)
+          && !crtl->calls_eh_return
+	  && !arm_pac_enabled_for_curr_function_p ())
         {
           saved_regs_mask &= ~(1 << LR_REGNUM);
           saved_regs_mask |= (1 << PC_REGNUM);
@@ -27688,6 +27738,9 @@ arm_expand_epilogue (bool really_return)
 	}
     }
 
+  if (arm_pac_enabled_for_curr_function_p ())
+    emit_insn (gen_aut_ip_lr_sp ());
+
   if (!really_return)
     return;
 
@@ -30393,6 +30446,9 @@ arm_conditional_register_usage (void)
 	global_regs[ARM_HARD_FRAME_POINTER_REGNUM] = 1;
     }
 
+  if (TARGET_HAVE_PACBTI)
+    call_used_regs[IP_REGNUM] = 1;
+
   /* The Q and GE bits are only accessed via special ACLE patterns.  */
   CLEAR_HARD_REG_BIT (operand_reg_set, APSRQ_REGNUM);
   CLEAR_HARD_REG_BIT (operand_reg_set, APSRGE_REGNUM);
@@ -32822,6 +32878,15 @@ arm_fusion_enabled_p (tune_params::fuse_ops op)
   return current_tune->fusible_ops & op;
 }
 
+/* Return TRUE if return address signing mechanism is enabled.  */
+static bool
+arm_pac_enabled_for_curr_function_p (void)
+{
+  return aarch_ra_sign_scope == AARCH_FUNCTION_ALL
+    || (aarch_ra_sign_scope == AARCH_FUNCTION_NON_LEAF
+	&& !crtl->is_leaf);
+}
+
 /* Implement TARGET_SCHED_CAN_SPECULATE_INSN.  Return true if INSN can be
    scheduled for speculative execution.  Reject the long-running division
    and square-root instructions.  */
diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index 4adc976b8b6..132135d244d 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -12823,6 +12823,26 @@
    (set_attr "length" "8")]
 )
 
+(define_insn "pac_ip_lr_sp"
+  [(set (reg:DI IP_REGNUM)
+	(unspec:DI [(reg:DI SP_REGNUM) (reg:DI LR_REGNUM)]
+                   UNSPEC_PAC_IP_LR_SP))]
+  ""
+  "pac\tr12, lr, sp")
+
+(define_insn "pacbti_ip_lr_sp"
+  [(set (reg:DI IP_REGNUM)
+	(unspec:DI [(reg:DI SP_REGNUM) (reg:DI LR_REGNUM)]
+                   UNSPEC_PACBTI_IP_LR_SP))]
+  ""
+  "pacbti\tr12, lr, sp")
+
+(define_insn "aut_ip_lr_sp"
+  [(unspec:DI [(reg:DI IP_REGNUM) (reg:DI SP_REGNUM) (reg:DI LR_REGNUM)]
+              UNSPEC_AUT_IP_LR_SP)]
+  ""
+  "aut\tr12, lr, sp")
+
 ;; Vector bits common to IWMMXT, Neon and MVE
 (include "vec-common.md")
 ;; Load the Intel Wireless Multimedia Extension patterns
diff --git a/gcc/config/arm/unspecs.md b/gcc/config/arm/unspecs.md
index ad1c6edd005..d60d0ceb87c 100644
--- a/gcc/config/arm/unspecs.md
+++ b/gcc/config/arm/unspecs.md
@@ -159,6 +159,9 @@
   UNSPEC_VCDE		; Custom Datapath Extension instruction.
   UNSPEC_VCDEA		; Custom Datapath Extension instruction.
   UNSPEC_DLS		; Used for DLS (Do Loop Start), Armv8.1-M Mainline instruction
+  UNSPEC_PAC_IP_LR_SP   ; Represents PAC signing LR
+  UNSPEC_PACBTI_IP_LR_SP ; Represents PAC signing LR + valid landing pad
+  UNSPEC_AUT_IP_LR_SP   ; Represents PAC verifying LR
 ])
 
 
diff --git a/gcc/testsuite/gcc.target/arm/pac-1.c b/gcc/testsuite/gcc.target/arm/pac-1.c
new file mode 100644
index 00000000000..8979a554e63
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pac-1.c
@@ -0,0 +1,25 @@
+/* Testing return address signing.  */
+/* { dg-do run } */
+/* { dg-options "-march=armv8.1-m.main -mbranch-protection=pac-ret+leaf -mthumb --save-temps -O0" } */
+
+#include <stdlib.h>
+
+int
+__attribute__((noinline))
+foo1 (int a, int b)
+{
+  return a + b;
+}
+
+int
+main (void)
+{
+  if (foo1 (1, 2) != 3)
+    abort ();
+
+  return 0;
+}
+
+/* { dg-final { scan-assembler-times "pac\tr12, lr, sp" 2 } } */
+/* { dg-final { scan-assembler-times "aut\tr12, lr, sp" 2 } } */
+/* { dg-final { scan-assembler-not "bti" } } */
diff --git a/gcc/testsuite/gcc.target/arm/pac-2.c b/gcc/testsuite/gcc.target/arm/pac-2.c
new file mode 100644
index 00000000000..678294af67d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pac-2.c
@@ -0,0 +1,25 @@
+/* Testing return address signing.  */
+/* { dg-do run } */
+/* { dg-options "-march=armv8.1-m.main -mbranch-protection=pac-ret -mthumb --save-temps -O0" } */
+
+#include <stdlib.h>
+
+int
+__attribute__((noinline))
+foo1 (int a, int b)
+{
+  return a + b;
+}
+
+int
+main (void)
+{
+  if (foo1 (1, 2) != 3)
+    abort ();
+
+  return 0;
+}
+
+/* { dg-final { scan-assembler "pac\tr12, lr, sp" } } */
+/* { dg-final { scan-assembler "aut\tr12, lr, sp" } } */
+/* { dg-final { scan-assembler-not "bti" } } */
diff --git a/gcc/testsuite/gcc.target/arm/pac-3.c b/gcc/testsuite/gcc.target/arm/pac-3.c
new file mode 100644
index 00000000000..e67ee910683
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pac-3.c
@@ -0,0 +1,25 @@
+/* Testing return address signing.  */
+/* { dg-do run } */
+/* { dg-options "-march=armv8.1-m.main -mbranch-protection=bti+pac-ret+leaf -mthumb --save-temps -O2" } */
+
+#include <stdlib.h>
+
+int
+__attribute__((noinline))
+foo1 (int a, int b)
+{
+  return a + b;
+}
+
+int
+main (void)
+{
+  if (foo1 (1, 2) != 3)
+    abort ();
+
+  return 0;
+}
+
+/* { dg-final { scan-assembler-times "pacbti\tr12, lr, sp" 2 } } */
+/* { dg-final { scan-assembler-times "aut\tr12, lr, sp" 2 } } */
+/* { dg-final { scan-assembler-not "\tbti\t" } } */
diff --git a/gcc/testsuite/gcc.target/arm/pac-4.c b/gcc/testsuite/gcc.target/arm/pac-4.c
new file mode 100644
index 00000000000..404457313a0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pac-4.c
@@ -0,0 +1,25 @@
+/* Testing return address signing.  */
+/* { dg-do run } */
+/* { dg-options "-march=armv8.1-m.main+pacbti -mthumb --save-temps -O2" } */
+
+#include <stdlib.h>
+
+int
+__attribute__((noinline))
+foo1 (int a, int b)
+{
+  return a + b;
+}
+
+int
+main (void)
+{
+  if (foo1 (1, 2) != 3)
+    abort ();
+
+  return 0;
+}
+
+/* { dg-final { scan-assembler-not "\tbti\t" } } */
+/* { dg-final { scan-assembler-not "\tpac\t" } } */
+/* { dg-final { scan-assembler-not "\tpacbti\t" } } */
diff --git a/gcc/testsuite/gcc.target/arm/pac-5.c b/gcc/testsuite/gcc.target/arm/pac-5.c
new file mode 100644
index 00000000000..d2d996b921a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pac-5.c
@@ -0,0 +1,26 @@
+/* Testing return address signing.  */
+/* { dg-do run } */
+/* { dg-options "-march=armv8.1-m.main -mbranch-protection=pac-ret+leaf -mthumb --save-temps -O0" } */
+
+#include <stdlib.h>
+
+int
+__attribute__((noinline))
+foo1 (int a, int b)
+{
+  int square (int z) { return z * z; }
+  return square (a) + square (b);
+}
+
+int
+main (void)
+{
+  if (foo1 (1, 2) != 5)
+    abort ();
+
+  return 0;
+}
+
+/* { dg-final { scan-assembler-times "pac\tr12, lr, sp" 3 } } */
+/* { dg-final { scan-assembler-times "aut\tr12, lr, sp" 3 } } */
+/* { dg-final { scan-assembler-not "bti" } } */
