[v5,1/4] RISC-V: Add Zicfiss ISA extension.

Message ID 20250116081334.95540-1-monk.chiang@sifive.com
State Committed
Commit dc76aa0e4d5398104b6b26f08b46524b97de5100
Headers
Series [v5,1/4] RISC-V: Add Zicfiss ISA extension. |

Checks

Context Check Description
rivoscibot/toolchain-ci-rivos-lint warning Lint failed
rivoscibot/toolchain-ci-rivos-apply-patch success Patch applied
rivoscibot/toolchain-ci-rivos-build--newlib-rv64gcv-lp64d-multilib success Build passed
rivoscibot/toolchain-ci-rivos-build--linux-rv64gcv-lp64d-multilib success Build passed
rivoscibot/toolchain-ci-rivos-build--linux-rv64gc_zba_zbb_zbc_zbs-lp64d-multilib success Build passed
rivoscibot/toolchain-ci-rivos-test success Testing passed

Commit Message

Monk Chiang Jan. 16, 2025, 8:13 a.m. UTC
  This patch is implemented according to the RISC-V CFI specification.
It supports the generation of shadow stack instructions in the prologue,
epilogue, non-local gotos, and unwinding.

RISC-V CFI SPEC: https://github.com/riscv/riscv-cfi

gcc/ChangeLog:
	* common/config/riscv/riscv-common.cc: Add ZICFISS ISA string.
	* gcc/config/riscv/predicates.md: New predicate x1x5_operand.
	* gcc/config/riscv/riscv.cc
	  (riscv_expand_prologue): Insert shadow stack instructions.
	  (riscv_expand_epilogue): Likewise.
	  (riscv_for_each_saved_reg): Assign t0 or ra register for
	  sspopchk instruction.
	  (need_shadow_stack_push_pop_p): New function. Omit shadow
	  stack operation on leaf function.
	* gcc/config/riscv/riscv.h
	  (need_shadow_stack_push_pop_p): Define.
	* gcc/config/riscv/riscv.md: Add shadow stack patterns.
	  (save_stack_nonlocal): Add shadow stack instructions for setjump.
	  (restore_stack_nonlocal): Add shadow stack instructions for longjump.

libgcc/ChangeLog:
	* gcc/config/riscv/riscv.opt (TARGET_ZICFISS): Define.
	* libgcc/config/riscv/linux-unwind.h: Include shadow-stack-unwind.h.
	* libgcc/config/riscv/shadow-stack-unwind.h
	  (_Unwind_Frames_Extra): Define.
	  (_Unwind_Frames_Increment): Define.

gcc/testsuite/ChangeLog:
	* gcc/testsuite/gcc.target/riscv/ssp-1.c: New test.
	* gcc/testsuite/gcc.target/riscv/ssp-2.c: New test.

Co-Developed-by: Greg McGary <gkm@rivosinc.com>,
		 Kito Cheng  <kito.cheng@gmail.com>
---
 gcc/common/config/riscv/riscv-common.cc   |   7 ++
 gcc/config/riscv/predicates.md            |   6 ++
 gcc/config/riscv/riscv.cc                 |  58 ++++++++--
 gcc/config/riscv/riscv.h                  |   1 +
 gcc/config/riscv/riscv.md                 | 125 +++++++++++++++++++++-
 gcc/config/riscv/riscv.opt                |   2 +
 gcc/testsuite/gcc.target/riscv/ssp-1.c    |  41 +++++++
 gcc/testsuite/gcc.target/riscv/ssp-2.c    |  10 ++
 libgcc/config/riscv/linux-unwind.h        |   5 +
 libgcc/config/riscv/shadow-stack-unwind.h |  74 +++++++++++++
 10 files changed, 320 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/riscv/ssp-1.c
 create mode 100644 gcc/testsuite/gcc.target/riscv/ssp-2.c
 create mode 100644 libgcc/config/riscv/shadow-stack-unwind.h
  

Comments

Kito Cheng Jan. 16, 2025, 10:23 a.m. UTC | #1
LGTM for the V5 series :)


On Thu, Jan 16, 2025 at 4:13 PM Monk Chiang <monk.chiang@sifive.com> wrote:
>
> This patch is implemented according to the RISC-V CFI specification.
> It supports the generation of shadow stack instructions in the prologue,
> epilogue, non-local gotos, and unwinding.
>
> RISC-V CFI SPEC: https://github.com/riscv/riscv-cfi
>
> gcc/ChangeLog:
>         * common/config/riscv/riscv-common.cc: Add ZICFISS ISA string.
>         * gcc/config/riscv/predicates.md: New predicate x1x5_operand.
>         * gcc/config/riscv/riscv.cc
>           (riscv_expand_prologue): Insert shadow stack instructions.
>           (riscv_expand_epilogue): Likewise.
>           (riscv_for_each_saved_reg): Assign t0 or ra register for
>           sspopchk instruction.
>           (need_shadow_stack_push_pop_p): New function. Omit shadow
>           stack operation on leaf function.
>         * gcc/config/riscv/riscv.h
>           (need_shadow_stack_push_pop_p): Define.
>         * gcc/config/riscv/riscv.md: Add shadow stack patterns.
>           (save_stack_nonlocal): Add shadow stack instructions for setjump.
>           (restore_stack_nonlocal): Add shadow stack instructions for longjump.
>
> libgcc/ChangeLog:
>         * gcc/config/riscv/riscv.opt (TARGET_ZICFISS): Define.
>         * libgcc/config/riscv/linux-unwind.h: Include shadow-stack-unwind.h.
>         * libgcc/config/riscv/shadow-stack-unwind.h
>           (_Unwind_Frames_Extra): Define.
>           (_Unwind_Frames_Increment): Define.
>
> gcc/testsuite/ChangeLog:
>         * gcc/testsuite/gcc.target/riscv/ssp-1.c: New test.
>         * gcc/testsuite/gcc.target/riscv/ssp-2.c: New test.
>
> Co-Developed-by: Greg McGary <gkm@rivosinc.com>,
>                  Kito Cheng  <kito.cheng@gmail.com>
> ---
>  gcc/common/config/riscv/riscv-common.cc   |   7 ++
>  gcc/config/riscv/predicates.md            |   6 ++
>  gcc/config/riscv/riscv.cc                 |  58 ++++++++--
>  gcc/config/riscv/riscv.h                  |   1 +
>  gcc/config/riscv/riscv.md                 | 125 +++++++++++++++++++++-
>  gcc/config/riscv/riscv.opt                |   2 +
>  gcc/testsuite/gcc.target/riscv/ssp-1.c    |  41 +++++++
>  gcc/testsuite/gcc.target/riscv/ssp-2.c    |  10 ++
>  libgcc/config/riscv/linux-unwind.h        |   5 +
>  libgcc/config/riscv/shadow-stack-unwind.h |  74 +++++++++++++
>  10 files changed, 320 insertions(+), 9 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.target/riscv/ssp-1.c
>  create mode 100644 gcc/testsuite/gcc.target/riscv/ssp-2.c
>  create mode 100644 libgcc/config/riscv/shadow-stack-unwind.h
>
> diff --git a/gcc/common/config/riscv/riscv-common.cc b/gcc/common/config/riscv/riscv-common.cc
> index bfc8aa559c5..8e8b6107a6d 100644
> --- a/gcc/common/config/riscv/riscv-common.cc
> +++ b/gcc/common/config/riscv/riscv-common.cc
> @@ -111,6 +111,9 @@ static const riscv_implied_info_t riscv_implied_info[] =
>    {"zfinx", "zicsr"},
>    {"zdinx", "zicsr"},
>
> +  {"zicfiss", "zicsr"},
> +  {"zicfiss", "zimop"},
> +
>    {"zk", "zkn"},
>    {"zk", "zkr"},
>    {"zk", "zkt"},
> @@ -325,6 +328,8 @@ static const struct riscv_ext_version riscv_ext_version_table[] =
>    {"zicclsm",  ISA_SPEC_CLASS_NONE, 1, 0},
>    {"ziccrse",  ISA_SPEC_CLASS_NONE, 1, 0},
>
> +  {"zicfiss", ISA_SPEC_CLASS_NONE, 1, 0},
> +
>    {"zimop", ISA_SPEC_CLASS_NONE, 1, 0},
>    {"zcmop", ISA_SPEC_CLASS_NONE, 1, 0},
>
> @@ -1647,6 +1652,8 @@ static const riscv_ext_flag_table_t riscv_ext_flag_table[] =
>    RISCV_EXT_FLAG_ENTRY ("zicbop", x_riscv_zicmo_subext, MASK_ZICBOP),
>    RISCV_EXT_FLAG_ENTRY ("zic64b", x_riscv_zicmo_subext, MASK_ZIC64B),
>
> +  RISCV_EXT_FLAG_ENTRY ("zicfiss", x_riscv_zi_subext, MASK_ZICFISS),
> +
>    RISCV_EXT_FLAG_ENTRY ("zimop", x_riscv_mop_subext, MASK_ZIMOP),
>    RISCV_EXT_FLAG_ENTRY ("zcmop", x_riscv_mop_subext, MASK_ZCMOP),
>
> diff --git a/gcc/config/riscv/predicates.md b/gcc/config/riscv/predicates.md
> index cda7502a62a..1f67d30be9d 100644
> --- a/gcc/config/riscv/predicates.md
> +++ b/gcc/config/riscv/predicates.md
> @@ -679,3 +679,9 @@
>    return (riscv_symbolic_constant_p (op, &type)
>           && type == SYMBOL_PCREL);
>  })
> +
> +;; Shadow stack operands only allow x1, x5 registers
> +(define_predicate "x1x5_operand"
> +  (and (match_operand 0 "register_operand")
> +       (match_test "REGNO (op) == RETURN_ADDR_REGNUM
> +                   || REGNO (op) == T0_REGNUM")))
> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
> index 65e09842fde..cd37b492183 100644
> --- a/gcc/config/riscv/riscv.cc
> +++ b/gcc/config/riscv/riscv.cc
> @@ -7496,6 +7496,9 @@ riscv_save_reg_p (unsigned int regno)
>        if (regno == GP_REGNUM || regno == THREAD_POINTER_REGNUM)
>         return false;
>
> +      if (regno == RETURN_ADDR_REGNUM && TARGET_ZICFISS)
> +       return true;
> +
>        /* We must save every register used in this function.  If this is not a
>          leaf function, then we must save all temporary registers.  */
>        if (df_regs_ever_live_p (regno)
> @@ -8049,7 +8052,7 @@ riscv_is_eh_return_data_register (unsigned int regno)
>
>  static void
>  riscv_for_each_saved_reg (poly_int64 sp_offset, riscv_save_restore_fn fn,
> -                         bool epilogue, bool maybe_eh_return)
> +                         bool epilogue, bool maybe_eh_return, bool sibcall_p)
>  {
>    HOST_WIDE_INT offset, first_fp_offset;
>    unsigned int regno, num_masked_fp = 0;
> @@ -8135,7 +8138,14 @@ riscv_for_each_saved_reg (poly_int64 sp_offset, riscv_save_restore_fn fn,
>             }
>         }
>
> -      riscv_save_restore_reg (word_mode, regno, offset, fn);
> +      if (need_shadow_stack_push_pop_p () && epilogue && !sibcall_p
> +         && !(maybe_eh_return && crtl->calls_eh_return)
> +         && (regno == RETURN_ADDR_REGNUM)
> +         && !cfun->machine->interrupt_handler_p)
> +       riscv_save_restore_reg (word_mode, RISCV_PROLOGUE_TEMP_REGNUM,
> +                               offset, fn);
> +      else
> +       riscv_save_restore_reg (word_mode, regno, offset, fn);
>      }
>
>    /* This loop must iterate over the same space as its companion in
> @@ -8729,6 +8739,9 @@ riscv_expand_prologue (void)
>    if (cfun->machine->naked_p)
>      return;
>
> +  if (need_shadow_stack_push_pop_p ())
> +    emit_insn (gen_sspush (Pmode, gen_rtx_REG (Pmode, RETURN_ADDR_REGNUM)));
> +
>    /* prefer multi-push to save-restore libcall.  */
>    if (riscv_use_multi_push (frame))
>      {
> @@ -8772,7 +8785,7 @@ riscv_expand_prologue (void)
>             = get_multi_push_fpr_mask (multi_push_additional / UNITS_PER_WORD);
>           frame->fmask &= mask_fprs_push;
>           riscv_for_each_saved_reg (remaining_size, riscv_save_reg, false,
> -                                   false);
> +                                   false, false);
>           frame->fmask = fmask & ~mask_fprs_push; /* mask for the rest FPRs.  */
>         }
>      }
> @@ -8823,7 +8836,8 @@ riscv_expand_prologue (void)
>                                 GEN_INT (-step1));
>           RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
>         }
> -      riscv_for_each_saved_reg (remaining_size, riscv_save_reg, false, false);
> +      riscv_for_each_saved_reg (remaining_size, riscv_save_reg,
> +                               false, false, false);
>      }
>
>    /* Undo the above fib.  */
> @@ -8985,6 +8999,7 @@ riscv_expand_epilogue (int style)
>      = use_multi_pop ? frame->multi_push_adj_base + frame->multi_push_adj_addi
>                     : 0;
>    rtx ra = gen_rtx_REG (Pmode, RETURN_ADDR_REGNUM);
> +  rtx t0 = gen_rtx_REG (Pmode, RISCV_PROLOGUE_TEMP_REGNUM);
>    unsigned th_int_mask = 0;
>    rtx insn;
>
> @@ -9194,7 +9209,8 @@ riscv_expand_epilogue (int style)
>    riscv_for_each_saved_v_reg (step2, riscv_restore_reg, false);
>    riscv_for_each_saved_reg (frame->total_size - step2 - libcall_size
>                               - multipop_size,
> -                           riscv_restore_reg, true, style == EXCEPTION_RETURN);
> +                           riscv_restore_reg, true, style == EXCEPTION_RETURN,
> +                           style == SIBCALL_RETURN);
>
>    if (th_int_mask && TH_INT_INTERRUPT (cfun))
>      {
> @@ -9234,7 +9250,8 @@ riscv_expand_epilogue (int style)
>         riscv_for_each_saved_reg (frame->total_size - libcall_size
>                                     - multipop_size,
>                                   riscv_restore_reg, true,
> -                                 style == EXCEPTION_RETURN);
> +                                 style == EXCEPTION_RETURN, false);
> +
>        /* Undo the above fib.  */
>        frame->mask = mask;
>        frame->fmask = fmask;
> @@ -9259,6 +9276,17 @@ riscv_expand_epilogue (int style)
>      emit_insn (gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx,
>                               EH_RETURN_STACKADJ_RTX));
>
> +  if (need_shadow_stack_push_pop_p ()
> +      && !((style == EXCEPTION_RETURN) && crtl->calls_eh_return))
> +    {
> +      if (BITSET_P (cfun->machine->frame.mask, RETURN_ADDR_REGNUM)
> +         && style != SIBCALL_RETURN
> +         && !cfun->machine->interrupt_handler_p)
> +       emit_insn (gen_sspopchk (Pmode, t0));
> +      else
> +       emit_insn (gen_sspopchk (Pmode, ra));
> +    }
> +
>    /* Return from interrupt.  */
>    if (cfun->machine->interrupt_handler_p)
>      {
> @@ -9276,7 +9304,15 @@ riscv_expand_epilogue (int style)
>         emit_jump_insn (gen_riscv_uret ());
>      }
>    else if (style != SIBCALL_RETURN)
> -    emit_jump_insn (gen_simple_return_internal (ra));
> +    {
> +      if (need_shadow_stack_push_pop_p ()
> +         && !((style == EXCEPTION_RETURN) && crtl->calls_eh_return)
> +         && BITSET_P (cfun->machine->frame.mask, RETURN_ADDR_REGNUM)
> +         && !cfun->machine->interrupt_handler_p)
> +       emit_jump_insn (gen_simple_return_internal (t0));
> +      else
> +       emit_jump_insn (gen_simple_return_internal (ra));
> +    }
>  }
>
>  /* Implement EPILOGUE_USES.  */
> @@ -9473,7 +9509,8 @@ bool
>  riscv_can_use_return_insn (void)
>  {
>    return (reload_completed && known_eq (cfun->machine->frame.total_size, 0)
> -         && ! cfun->machine->interrupt_handler_p);
> +         && ! cfun->machine->interrupt_handler_p
> +         && ! need_shadow_stack_push_pop_p ());
>  }
>
>  /* Given that there exists at least one variable that is set (produced)
> @@ -13786,6 +13823,11 @@ expand_reversed_crc_using_clmul (scalar_mode crc_mode, scalar_mode data_mode,
>    riscv_emit_move (operands[0], gen_lowpart (crc_mode, a0));
>  }
>
> +bool need_shadow_stack_push_pop_p ()
> +{
> +  return TARGET_ZICFISS && riscv_save_return_addr_reg_p ();
> +}
> +
>  /* 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/riscv/riscv.h b/gcc/config/riscv/riscv.h
> index 4ff883af16b..93e88fe885d 100644
> --- a/gcc/config/riscv/riscv.h
> +++ b/gcc/config/riscv/riscv.h
> @@ -1189,6 +1189,7 @@ extern poly_int64 riscv_v_adjust_nunits (enum machine_mode, int);
>  extern poly_int64 riscv_v_adjust_nunits (machine_mode, bool, int, int);
>  extern poly_int64 riscv_v_adjust_precision (enum machine_mode, int);
>  extern poly_int64 riscv_v_adjust_bytesize (enum machine_mode, int);
> +extern bool need_shadow_stack_push_pop_p ();
>  /* The number of bits and bytes in a RVV vector.  */
>  #define BITS_PER_RISCV_VECTOR (poly_uint16 (riscv_vector_chunks * riscv_bytes_per_vector_chunk * 8))
>  #define BYTES_PER_RISCV_VECTOR (poly_uint16 (riscv_vector_chunks * riscv_bytes_per_vector_chunk))
> diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
> index 0922cab4402..480e6c8856d 100644
> --- a/gcc/config/riscv/riscv.md
> +++ b/gcc/config/riscv/riscv.md
> @@ -137,6 +137,12 @@
>    ;; Zihintpause unspec
>    UNSPECV_PAUSE
>
> +  ;; ZICFISS
> +  UNSPECV_SSPUSH
> +  UNSPECV_SSPOPCHK
> +  UNSPECV_SSRDP
> +  UNSPECV_SSP
> +
>    ;; XTheadInt unspec
>    UNSPECV_XTHEADINT_PUSH
>    UNSPECV_XTHEADINT_POP
> @@ -4116,6 +4122,30 @@
>     (set_attr "length" "0")]
>  )
>
> +(define_expand "save_stack_nonlocal"
> +  [(set (match_operand 0 "memory_operand")
> +       (match_operand 1 "register_operand"))]
> +  ""
> +{
> +  rtx stack_slot;
> +
> +  if (need_shadow_stack_push_pop_p ())
> +    {
> +      /* Copy shadow stack pointer to the first slot
> +        and stack pointer to the second slot.  */
> +      rtx ssp_slot = adjust_address (operands[0], word_mode, 0);
> +      stack_slot = adjust_address (operands[0], Pmode, UNITS_PER_WORD);
> +
> +      rtx reg_ssp = force_reg (word_mode, const0_rtx);
> +      emit_insn (gen_ssrdp (word_mode, reg_ssp));
> +      emit_move_insn (ssp_slot, reg_ssp);
> +    }
> +  else
> +    stack_slot = adjust_address (operands[0], Pmode, 0);
> +  emit_move_insn (stack_slot, operands[1]);
> +  DONE;
> +})
> +
>  ;; This fixes a failure with gcc.c-torture/execute/pr64242.c at -O2 for a
>  ;; 32-bit target when using -mtune=sifive-7-series.  The first sched pass
>  ;; runs before register elimination, and we have a non-obvious dependency
> @@ -4126,7 +4156,70 @@
>     (match_operand 1 "memory_operand")]
>    ""
>  {
> -  emit_move_insn (operands[0], operands[1]);
> +  rtx stack_slot;
> +
> +  if (need_shadow_stack_push_pop_p ())
> +    {
> +      rtx t0 = gen_rtx_REG (Pmode, RISCV_PROLOGUE_TEMP_REGNUM);
> +      /* Restore shadow stack pointer from the first slot
> +        and stack pointer from the second slot.  */
> +      rtx ssp_slot = adjust_address (operands[1], word_mode, 0);
> +      stack_slot = adjust_address (operands[1], Pmode, UNITS_PER_WORD);
> +
> +      /* Get the current shadow stack pointer.  */
> +      rtx cur_ssp = force_reg (word_mode, const0_rtx);
> +      emit_insn (gen_ssrdp (word_mode, cur_ssp));
> +
> +      /* Compare and jump over adjustment code.  */
> +      rtx noadj_label = gen_label_rtx ();
> +      emit_cmp_and_jump_insns (cur_ssp, const0_rtx, EQ, NULL_RTX,
> +                              word_mode, 1, noadj_label);
> +
> +      rtx loop_label = gen_label_rtx ();
> +      emit_label (loop_label);
> +      LABEL_NUSES (loop_label) = 1;
> +
> +      /* Check if current ssp less than jump buffer ssp,
> +        so no loop is needed.  */
> +      emit_cmp_and_jump_insns (ssp_slot, cur_ssp, LE, NULL_RTX,
> +                              ptr_mode, 1, noadj_label);
> +
> +      /* Advance by a maximum of 4K at a time to avoid unwinding
> +        past bounds of the shadow stack.  */
> +      rtx reg_4096 = force_reg (word_mode, GEN_INT (4096));
> +      rtx cmp_ssp  = gen_reg_rtx (word_mode);
> +      cmp_ssp = expand_simple_binop (ptr_mode, MINUS,
> +                                    ssp_slot, cur_ssp,
> +                                    cmp_ssp, 1, OPTAB_DIRECT);
> +
> +      /* Update curr_ssp from jump buffer ssp.  */
> +      emit_move_insn (cur_ssp, ssp_slot);
> +      emit_insn (gen_write_ssp (word_mode, cur_ssp));
> +      emit_jump_insn (gen_jump (loop_label));
> +      emit_barrier ();
> +
> +      /* Adjust the ssp in a loop.  */
> +      rtx cmp_4k_label = gen_label_rtx ();
> +      emit_label (cmp_4k_label);
> +      LABEL_NUSES (cmp_4k_label) = 1;
> +
> +      /* Add 4k for curr_ssp.  */
> +      cur_ssp = expand_simple_binop (ptr_mode, PLUS,
> +                                    cur_ssp, reg_4096,
> +                                    cur_ssp, 1, OPTAB_DIRECT);
> +      emit_insn (gen_write_ssp (word_mode, cur_ssp));
> +      emit_insn (gen_sspush (Pmode, t0));
> +      emit_insn (gen_sspopchk (Pmode, t0));
> +      emit_jump_insn (gen_jump (loop_label));
> +      emit_barrier ();
> +
> +      emit_label (noadj_label);
> +      LABEL_NUSES (noadj_label) = 1;
> +    }
> +  else
> +    stack_slot = adjust_address (operands[1], Pmode, 0);
> +
> +  emit_move_insn (operands[0], stack_slot);
>    /* Prevent the following hard fp restore from being moved before the move
>       insn above which uses a copy of the soft fp reg.  */
>    emit_clobber (gen_rtx_MEM (BLKmode, hard_frame_pointer_rtx));
> @@ -4594,6 +4687,36 @@
>     }"
>    [(set_attr "type" "arith")])
>
> +;; Shadow stack
> +
> +(define_insn "@sspush<mode>"
> +  [(unspec_volatile [(match_operand:P 0 "x1x5_operand" "r")] UNSPECV_SSPUSH)]
> +  "TARGET_ZICFISS"
> +  "sspush\t%0"
> +  [(set_attr "type" "arith")
> +   (set_attr "mode" "<MODE>")])
> +
> +(define_insn "@sspopchk<mode>"
> +  [(unspec_volatile [(match_operand:P 0 "x1x5_operand" "r")] UNSPECV_SSPOPCHK)]
> +  "TARGET_ZICFISS"
> +  "sspopchk\t%0"
> +  [(set_attr "type" "arith")
> +   (set_attr "mode" "<MODE>")])
> +
> +(define_insn "@ssrdp<mode>"
> +  [(set (match_operand:P 0 "register_operand" "=r")
> +       (unspec_volatile [(const_int 0)] UNSPECV_SSRDP))]
> +  "TARGET_ZICFISS"
> +  "ssrdp\t%0"
> +  [(set_attr "type" "arith")
> +   (set_attr "mode" "<MODE>")])
> +
> +(define_insn "@write_ssp<mode>"
> +  [(unspec_volatile [(match_operand:P 0 "register_operand" "r")] UNSPECV_SSP)]
> +  "TARGET_ZICFISS"
> +  "csrw\tssp, %0"
> +  [(set_attr "type" "arith")
> +   (set_attr "mode" "<MODE>")])
>
>  (include "bitmanip.md")
>  (include "crypto.md")
> diff --git a/gcc/config/riscv/riscv.opt b/gcc/config/riscv/riscv.opt
> index 88fec1c8280..4a43bfd7bc6 100644
> --- a/gcc/config/riscv/riscv.opt
> +++ b/gcc/config/riscv/riscv.opt
> @@ -253,6 +253,8 @@ Mask(ZICCLSM)     Var(riscv_zi_subext)
>
>  Mask(ZICCRSE)     Var(riscv_zi_subext)
>
> +Mask(ZICFISS)     Var(riscv_zi_subext)
> +
>  TargetVariable
>  int riscv_za_subext
>
> diff --git a/gcc/testsuite/gcc.target/riscv/ssp-1.c b/gcc/testsuite/gcc.target/riscv/ssp-1.c
> new file mode 100644
> index 00000000000..abf47ec6442
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/riscv/ssp-1.c
> @@ -0,0 +1,41 @@
> +/* { dg-do compile { target { riscv64*-*-* } } } */
> +/* { dg-options "-O2 -march=rv64gc_zicfiss -mabi=lp64d" } */
> +/* { dg-skip-if "" { *-*-* } { "-O0" } } */
> +struct ad {
> +  void *ae;
> +};
> +struct af {
> +  union {
> +    int *ai;
> +    int *aj;
> +    struct ad *ak;
> +  } u;
> +  struct {
> +    struct {
> +      long al : 1;
> +      long am : 1;
> +      long : 21;
> +    } b;
> +    long i;
> +  } s;
> +};
> +void fdes (struct af *, void *, long *);
> +
> +void foo (struct af *bv, long *bw) {
> +  bw[0] = bw[1] = 0;
> +  if (bv->s.b.al)
> +    fdes (bv, bv->u.ak->ae, bw);
> +  else if (bv->s.b.am) {
> +    int **p = (int**)bv->u.aj;
> +    for (; *p; ++p)
> +     fdes (bv, *p, bw);
> +  } else
> +    fdes (bv, bv->u.ai, bw);
> +}
> +
> +/* { dg-final { scan-assembler-times "ld\tt0" 1 } } */
> +/* { dg-final { scan-assembler-times "sspopchk\tt0" 1 } } */
> +/* { dg-final { scan-assembler-times "jr\tt0" 1 } } */
> +/* { dg-final { scan-assembler-times "ld\tra" 2 } } */
> +/* { dg-final { scan-assembler-times "sspopchk\tra" 2 } } */
> +/* { dg-final { scan-assembler-times "tail" 2 } } */
> diff --git a/gcc/testsuite/gcc.target/riscv/ssp-2.c b/gcc/testsuite/gcc.target/riscv/ssp-2.c
> new file mode 100644
> index 00000000000..7c6098357e9
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/riscv/ssp-2.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile { target { riscv64*-*-* } } } */
> +/* { dg-options "-O0 -march=rv64gc_zicfiss -mabi=lp64d" } */
> +
> +void __attribute__ ((interrupt))
> +foo (void)
> +{
> +}
> +/* { dg-final { scan-assembler-times "sd\tra" 1 } } */
> +/* { dg-final { scan-assembler-times "ld\tra" 1 } } */
> +/* { dg-final { scan-assembler-times "sspopchk\tra" 1 } } */
> diff --git a/libgcc/config/riscv/linux-unwind.h b/libgcc/config/riscv/linux-unwind.h
> index 40c8f51d6fc..db755cdc6a8 100644
> --- a/libgcc/config/riscv/linux-unwind.h
> +++ b/libgcc/config/riscv/linux-unwind.h
> @@ -19,6 +19,11 @@
>     see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
>     <http://www.gnu.org/licenses/>.  */
>
> +/* Unwind shadow stack.  */
> +#if defined(__riscv_zicfiss)
> +# include "config/riscv/shadow-stack-unwind.h"
> +#endif
> +
>  #ifndef inhibit_libc
>
>  #include <signal.h>
> diff --git a/libgcc/config/riscv/shadow-stack-unwind.h b/libgcc/config/riscv/shadow-stack-unwind.h
> new file mode 100644
> index 00000000000..a978a54a4ca
> --- /dev/null
> +++ b/libgcc/config/riscv/shadow-stack-unwind.h
> @@ -0,0 +1,74 @@
> +/* _Unwind_Frames_Extra with shadow stack.
> +   Copyright (C) 2016-2025 Free Software Foundation, Inc.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify
> +it under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful,
> +but WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +GNU General Public License for more details.
> +
> +Under Section 7 of GPL version 3, you are granted additional
> +permissions described in the GCC Runtime Library Exception, version
> +3.1, as published by the Free Software Foundation.
> +
> +You should have received a copy of the GNU General Public License and
> +a copy of the GCC Runtime Library Exception along with this program;
> +see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
> +<http://www.gnu.org/licenses/>.  */
> +
> +#define LIBGCC2_UNITS_PER_WORD (__riscv_xlen / 8)
> +
> +/* Unwind the shadow stack for EH.  */
> +#undef _Unwind_Frames_Extra
> +#define _Unwind_Frames_Extra(x)                                        \
> +  do                                                           \
> +    {                                                          \
> +      _Unwind_Word ssp = 0;                                    \
> +      asm volatile ("ssrdp %0" : "=r"(ssp));                   \
> +      if (ssp != 0)                                            \
> +       {                                                       \
> +         _Unwind_Word tmp = (x);                               \
> +         tmp = tmp * LIBGCC2_UNITS_PER_WORD;                   \
> +         while (tmp > 4096)                                    \
> +           {                                                   \
> +             ssp += 4096;                                      \
> +             tmp -= 4096;                                      \
> +             asm volatile ("csrw ssp, %0" :: "r"(ssp));        \
> +             asm volatile ("sspush x5\n"                       \
> +                           "sspopchk x5" : : : "x5");          \
> +           }                                                   \
> +                                                               \
> +         if (tmp > 0)                                          \
> +           {                                                   \
> +             ssp += tmp;                                       \
> +             asm volatile ("csrw ssp, %0" :: "r"(ssp));        \
> +           }                                                   \
> +       }                                                       \
> +    }                                                          \
> +    while (0)
> +
> +#undef _Unwind_Frames_Increment
> +#define _Unwind_Frames_Increment(exc, context, frames) \
> +    {                                                  \
> +      frames++;                                                \
> +      if (exc->exception_class != 0                    \
> +         && _Unwind_GetIP (context) != 0               \
> +         && !_Unwind_IsSignalFrame (context))          \
> +       {                                               \
> +         _Unwind_Word ssp;                             \
> +         asm volatile ("ssrdp %0" : "=r"(ssp));        \
> +         if (ssp != 0)                                 \
> +           {                                           \
> +             ssp += LIBGCC2_UNITS_PER_WORD * frames;   \
> +             _Unwind_Word ra = *(_Unwind_Word *) ssp;  \
> +             if (ra != _Unwind_GetIP (context))        \
> +               return _URC_FATAL_PHASE2_ERROR;         \
> +           }                                           \
> +       }                                               \
> +    }
> --
> 2.47.1
>
  

Patch

diff --git a/gcc/common/config/riscv/riscv-common.cc b/gcc/common/config/riscv/riscv-common.cc
index bfc8aa559c5..8e8b6107a6d 100644
--- a/gcc/common/config/riscv/riscv-common.cc
+++ b/gcc/common/config/riscv/riscv-common.cc
@@ -111,6 +111,9 @@  static const riscv_implied_info_t riscv_implied_info[] =
   {"zfinx", "zicsr"},
   {"zdinx", "zicsr"},
 
+  {"zicfiss", "zicsr"},
+  {"zicfiss", "zimop"},
+
   {"zk", "zkn"},
   {"zk", "zkr"},
   {"zk", "zkt"},
@@ -325,6 +328,8 @@  static const struct riscv_ext_version riscv_ext_version_table[] =
   {"zicclsm",  ISA_SPEC_CLASS_NONE, 1, 0},
   {"ziccrse",  ISA_SPEC_CLASS_NONE, 1, 0},
 
+  {"zicfiss", ISA_SPEC_CLASS_NONE, 1, 0},
+
   {"zimop", ISA_SPEC_CLASS_NONE, 1, 0},
   {"zcmop", ISA_SPEC_CLASS_NONE, 1, 0},
 
@@ -1647,6 +1652,8 @@  static const riscv_ext_flag_table_t riscv_ext_flag_table[] =
   RISCV_EXT_FLAG_ENTRY ("zicbop", x_riscv_zicmo_subext, MASK_ZICBOP),
   RISCV_EXT_FLAG_ENTRY ("zic64b", x_riscv_zicmo_subext, MASK_ZIC64B),
 
+  RISCV_EXT_FLAG_ENTRY ("zicfiss", x_riscv_zi_subext, MASK_ZICFISS),
+
   RISCV_EXT_FLAG_ENTRY ("zimop", x_riscv_mop_subext, MASK_ZIMOP),
   RISCV_EXT_FLAG_ENTRY ("zcmop", x_riscv_mop_subext, MASK_ZCMOP),
 
diff --git a/gcc/config/riscv/predicates.md b/gcc/config/riscv/predicates.md
index cda7502a62a..1f67d30be9d 100644
--- a/gcc/config/riscv/predicates.md
+++ b/gcc/config/riscv/predicates.md
@@ -679,3 +679,9 @@ 
   return (riscv_symbolic_constant_p (op, &type)
          && type == SYMBOL_PCREL);
 })
+
+;; Shadow stack operands only allow x1, x5 registers
+(define_predicate "x1x5_operand"
+  (and (match_operand 0 "register_operand")
+       (match_test "REGNO (op) == RETURN_ADDR_REGNUM
+		    || REGNO (op) == T0_REGNUM")))
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 65e09842fde..cd37b492183 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -7496,6 +7496,9 @@  riscv_save_reg_p (unsigned int regno)
       if (regno == GP_REGNUM || regno == THREAD_POINTER_REGNUM)
 	return false;
 
+      if (regno == RETURN_ADDR_REGNUM && TARGET_ZICFISS)
+	return true;
+
       /* We must save every register used in this function.  If this is not a
 	 leaf function, then we must save all temporary registers.  */
       if (df_regs_ever_live_p (regno)
@@ -8049,7 +8052,7 @@  riscv_is_eh_return_data_register (unsigned int regno)
 
 static void
 riscv_for_each_saved_reg (poly_int64 sp_offset, riscv_save_restore_fn fn,
-			  bool epilogue, bool maybe_eh_return)
+			  bool epilogue, bool maybe_eh_return, bool sibcall_p)
 {
   HOST_WIDE_INT offset, first_fp_offset;
   unsigned int regno, num_masked_fp = 0;
@@ -8135,7 +8138,14 @@  riscv_for_each_saved_reg (poly_int64 sp_offset, riscv_save_restore_fn fn,
 	    }
 	}
 
-      riscv_save_restore_reg (word_mode, regno, offset, fn);
+      if (need_shadow_stack_push_pop_p () && epilogue && !sibcall_p
+	  && !(maybe_eh_return && crtl->calls_eh_return)
+	  && (regno == RETURN_ADDR_REGNUM)
+	  && !cfun->machine->interrupt_handler_p)
+	riscv_save_restore_reg (word_mode, RISCV_PROLOGUE_TEMP_REGNUM,
+				offset, fn);
+      else
+	riscv_save_restore_reg (word_mode, regno, offset, fn);
     }
 
   /* This loop must iterate over the same space as its companion in
@@ -8729,6 +8739,9 @@  riscv_expand_prologue (void)
   if (cfun->machine->naked_p)
     return;
 
+  if (need_shadow_stack_push_pop_p ())
+    emit_insn (gen_sspush (Pmode, gen_rtx_REG (Pmode, RETURN_ADDR_REGNUM)));
+
   /* prefer multi-push to save-restore libcall.  */
   if (riscv_use_multi_push (frame))
     {
@@ -8772,7 +8785,7 @@  riscv_expand_prologue (void)
 	    = get_multi_push_fpr_mask (multi_push_additional / UNITS_PER_WORD);
 	  frame->fmask &= mask_fprs_push;
 	  riscv_for_each_saved_reg (remaining_size, riscv_save_reg, false,
-				    false);
+				    false, false);
 	  frame->fmask = fmask & ~mask_fprs_push; /* mask for the rest FPRs.  */
 	}
     }
@@ -8823,7 +8836,8 @@  riscv_expand_prologue (void)
 				GEN_INT (-step1));
 	  RTX_FRAME_RELATED_P (emit_insn (insn)) = 1;
 	}
-      riscv_for_each_saved_reg (remaining_size, riscv_save_reg, false, false);
+      riscv_for_each_saved_reg (remaining_size, riscv_save_reg,
+				false, false, false);
     }
 
   /* Undo the above fib.  */
@@ -8985,6 +8999,7 @@  riscv_expand_epilogue (int style)
     = use_multi_pop ? frame->multi_push_adj_base + frame->multi_push_adj_addi
 		    : 0;
   rtx ra = gen_rtx_REG (Pmode, RETURN_ADDR_REGNUM);
+  rtx t0 = gen_rtx_REG (Pmode, RISCV_PROLOGUE_TEMP_REGNUM);
   unsigned th_int_mask = 0;
   rtx insn;
 
@@ -9194,7 +9209,8 @@  riscv_expand_epilogue (int style)
   riscv_for_each_saved_v_reg (step2, riscv_restore_reg, false);
   riscv_for_each_saved_reg (frame->total_size - step2 - libcall_size
 			      - multipop_size,
-			    riscv_restore_reg, true, style == EXCEPTION_RETURN);
+			    riscv_restore_reg, true, style == EXCEPTION_RETURN,
+			    style == SIBCALL_RETURN);
 
   if (th_int_mask && TH_INT_INTERRUPT (cfun))
     {
@@ -9234,7 +9250,8 @@  riscv_expand_epilogue (int style)
 	riscv_for_each_saved_reg (frame->total_size - libcall_size
 				    - multipop_size,
 				  riscv_restore_reg, true,
-				  style == EXCEPTION_RETURN);
+				  style == EXCEPTION_RETURN, false);
+
       /* Undo the above fib.  */
       frame->mask = mask;
       frame->fmask = fmask;
@@ -9259,6 +9276,17 @@  riscv_expand_epilogue (int style)
     emit_insn (gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx,
 			      EH_RETURN_STACKADJ_RTX));
 
+  if (need_shadow_stack_push_pop_p ()
+      && !((style == EXCEPTION_RETURN) && crtl->calls_eh_return))
+    {
+      if (BITSET_P (cfun->machine->frame.mask, RETURN_ADDR_REGNUM)
+	  && style != SIBCALL_RETURN
+	  && !cfun->machine->interrupt_handler_p)
+	emit_insn (gen_sspopchk (Pmode, t0));
+      else
+	emit_insn (gen_sspopchk (Pmode, ra));
+    }
+
   /* Return from interrupt.  */
   if (cfun->machine->interrupt_handler_p)
     {
@@ -9276,7 +9304,15 @@  riscv_expand_epilogue (int style)
 	emit_jump_insn (gen_riscv_uret ());
     }
   else if (style != SIBCALL_RETURN)
-    emit_jump_insn (gen_simple_return_internal (ra));
+    {
+      if (need_shadow_stack_push_pop_p ()
+	  && !((style == EXCEPTION_RETURN) && crtl->calls_eh_return)
+	  && BITSET_P (cfun->machine->frame.mask, RETURN_ADDR_REGNUM)
+	  && !cfun->machine->interrupt_handler_p)
+	emit_jump_insn (gen_simple_return_internal (t0));
+      else
+	emit_jump_insn (gen_simple_return_internal (ra));
+    }
 }
 
 /* Implement EPILOGUE_USES.  */
@@ -9473,7 +9509,8 @@  bool
 riscv_can_use_return_insn (void)
 {
   return (reload_completed && known_eq (cfun->machine->frame.total_size, 0)
-	  && ! cfun->machine->interrupt_handler_p);
+	  && ! cfun->machine->interrupt_handler_p
+	  && ! need_shadow_stack_push_pop_p ());
 }
 
 /* Given that there exists at least one variable that is set (produced)
@@ -13786,6 +13823,11 @@  expand_reversed_crc_using_clmul (scalar_mode crc_mode, scalar_mode data_mode,
   riscv_emit_move (operands[0], gen_lowpart (crc_mode, a0));
 }
 
+bool need_shadow_stack_push_pop_p ()
+{
+  return TARGET_ZICFISS && riscv_save_return_addr_reg_p ();
+}
+
 /* 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/riscv/riscv.h b/gcc/config/riscv/riscv.h
index 4ff883af16b..93e88fe885d 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -1189,6 +1189,7 @@  extern poly_int64 riscv_v_adjust_nunits (enum machine_mode, int);
 extern poly_int64 riscv_v_adjust_nunits (machine_mode, bool, int, int);
 extern poly_int64 riscv_v_adjust_precision (enum machine_mode, int);
 extern poly_int64 riscv_v_adjust_bytesize (enum machine_mode, int);
+extern bool need_shadow_stack_push_pop_p ();
 /* The number of bits and bytes in a RVV vector.  */
 #define BITS_PER_RISCV_VECTOR (poly_uint16 (riscv_vector_chunks * riscv_bytes_per_vector_chunk * 8))
 #define BYTES_PER_RISCV_VECTOR (poly_uint16 (riscv_vector_chunks * riscv_bytes_per_vector_chunk))
diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
index 0922cab4402..480e6c8856d 100644
--- a/gcc/config/riscv/riscv.md
+++ b/gcc/config/riscv/riscv.md
@@ -137,6 +137,12 @@ 
   ;; Zihintpause unspec
   UNSPECV_PAUSE
 
+  ;; ZICFISS
+  UNSPECV_SSPUSH
+  UNSPECV_SSPOPCHK
+  UNSPECV_SSRDP
+  UNSPECV_SSP
+
   ;; XTheadInt unspec
   UNSPECV_XTHEADINT_PUSH
   UNSPECV_XTHEADINT_POP
@@ -4116,6 +4122,30 @@ 
    (set_attr "length" "0")]
 )
 
+(define_expand "save_stack_nonlocal"
+  [(set (match_operand 0 "memory_operand")
+	(match_operand 1 "register_operand"))]
+  ""
+{
+  rtx stack_slot;
+
+  if (need_shadow_stack_push_pop_p ())
+    {
+      /* Copy shadow stack pointer to the first slot
+	 and stack pointer to the second slot.  */
+      rtx ssp_slot = adjust_address (operands[0], word_mode, 0);
+      stack_slot = adjust_address (operands[0], Pmode, UNITS_PER_WORD);
+
+      rtx reg_ssp = force_reg (word_mode, const0_rtx);
+      emit_insn (gen_ssrdp (word_mode, reg_ssp));
+      emit_move_insn (ssp_slot, reg_ssp);
+    }
+  else
+    stack_slot = adjust_address (operands[0], Pmode, 0);
+  emit_move_insn (stack_slot, operands[1]);
+  DONE;
+})
+
 ;; This fixes a failure with gcc.c-torture/execute/pr64242.c at -O2 for a
 ;; 32-bit target when using -mtune=sifive-7-series.  The first sched pass
 ;; runs before register elimination, and we have a non-obvious dependency
@@ -4126,7 +4156,70 @@ 
    (match_operand 1 "memory_operand")]
   ""
 {
-  emit_move_insn (operands[0], operands[1]);
+  rtx stack_slot;
+
+  if (need_shadow_stack_push_pop_p ())
+    {
+      rtx t0 = gen_rtx_REG (Pmode, RISCV_PROLOGUE_TEMP_REGNUM);
+      /* Restore shadow stack pointer from the first slot
+	 and stack pointer from the second slot.  */
+      rtx ssp_slot = adjust_address (operands[1], word_mode, 0);
+      stack_slot = adjust_address (operands[1], Pmode, UNITS_PER_WORD);
+
+      /* Get the current shadow stack pointer.  */
+      rtx cur_ssp = force_reg (word_mode, const0_rtx);
+      emit_insn (gen_ssrdp (word_mode, cur_ssp));
+
+      /* Compare and jump over adjustment code.  */
+      rtx noadj_label = gen_label_rtx ();
+      emit_cmp_and_jump_insns (cur_ssp, const0_rtx, EQ, NULL_RTX,
+			       word_mode, 1, noadj_label);
+
+      rtx loop_label = gen_label_rtx ();
+      emit_label (loop_label);
+      LABEL_NUSES (loop_label) = 1;
+
+      /* Check if current ssp less than jump buffer ssp,
+	 so no loop is needed.  */
+      emit_cmp_and_jump_insns (ssp_slot, cur_ssp, LE, NULL_RTX,
+			       ptr_mode, 1, noadj_label);
+
+      /* Advance by a maximum of 4K at a time to avoid unwinding
+	 past bounds of the shadow stack.  */
+      rtx reg_4096 = force_reg (word_mode, GEN_INT (4096));
+      rtx cmp_ssp  = gen_reg_rtx (word_mode);
+      cmp_ssp = expand_simple_binop (ptr_mode, MINUS,
+				     ssp_slot, cur_ssp,
+				     cmp_ssp, 1, OPTAB_DIRECT);
+
+      /* Update curr_ssp from jump buffer ssp.  */
+      emit_move_insn (cur_ssp, ssp_slot);
+      emit_insn (gen_write_ssp (word_mode, cur_ssp));
+      emit_jump_insn (gen_jump (loop_label));
+      emit_barrier ();
+
+      /* Adjust the ssp in a loop.  */
+      rtx cmp_4k_label = gen_label_rtx ();
+      emit_label (cmp_4k_label);
+      LABEL_NUSES (cmp_4k_label) = 1;
+
+      /* Add 4k for curr_ssp.  */
+      cur_ssp = expand_simple_binop (ptr_mode, PLUS,
+				     cur_ssp, reg_4096,
+				     cur_ssp, 1, OPTAB_DIRECT);
+      emit_insn (gen_write_ssp (word_mode, cur_ssp));
+      emit_insn (gen_sspush (Pmode, t0));
+      emit_insn (gen_sspopchk (Pmode, t0));
+      emit_jump_insn (gen_jump (loop_label));
+      emit_barrier ();
+
+      emit_label (noadj_label);
+      LABEL_NUSES (noadj_label) = 1;
+    }
+  else
+    stack_slot = adjust_address (operands[1], Pmode, 0);
+
+  emit_move_insn (operands[0], stack_slot);
   /* Prevent the following hard fp restore from being moved before the move
      insn above which uses a copy of the soft fp reg.  */
   emit_clobber (gen_rtx_MEM (BLKmode, hard_frame_pointer_rtx));
@@ -4594,6 +4687,36 @@ 
    }"
   [(set_attr "type" "arith")])
 
+;; Shadow stack
+
+(define_insn "@sspush<mode>"
+  [(unspec_volatile [(match_operand:P 0 "x1x5_operand" "r")] UNSPECV_SSPUSH)]
+  "TARGET_ZICFISS"
+  "sspush\t%0"
+  [(set_attr "type" "arith")
+   (set_attr "mode" "<MODE>")])
+
+(define_insn "@sspopchk<mode>"
+  [(unspec_volatile [(match_operand:P 0 "x1x5_operand" "r")] UNSPECV_SSPOPCHK)]
+  "TARGET_ZICFISS"
+  "sspopchk\t%0"
+  [(set_attr "type" "arith")
+   (set_attr "mode" "<MODE>")])
+
+(define_insn "@ssrdp<mode>"
+  [(set (match_operand:P 0 "register_operand" "=r")
+	(unspec_volatile [(const_int 0)] UNSPECV_SSRDP))]
+  "TARGET_ZICFISS"
+  "ssrdp\t%0"
+  [(set_attr "type" "arith")
+   (set_attr "mode" "<MODE>")])
+
+(define_insn "@write_ssp<mode>"
+  [(unspec_volatile [(match_operand:P 0 "register_operand" "r")] UNSPECV_SSP)]
+  "TARGET_ZICFISS"
+  "csrw\tssp, %0"
+  [(set_attr "type" "arith")
+   (set_attr "mode" "<MODE>")])
 
 (include "bitmanip.md")
 (include "crypto.md")
diff --git a/gcc/config/riscv/riscv.opt b/gcc/config/riscv/riscv.opt
index 88fec1c8280..4a43bfd7bc6 100644
--- a/gcc/config/riscv/riscv.opt
+++ b/gcc/config/riscv/riscv.opt
@@ -253,6 +253,8 @@  Mask(ZICCLSM)     Var(riscv_zi_subext)
 
 Mask(ZICCRSE)     Var(riscv_zi_subext)
 
+Mask(ZICFISS)     Var(riscv_zi_subext)
+
 TargetVariable
 int riscv_za_subext
 
diff --git a/gcc/testsuite/gcc.target/riscv/ssp-1.c b/gcc/testsuite/gcc.target/riscv/ssp-1.c
new file mode 100644
index 00000000000..abf47ec6442
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/ssp-1.c
@@ -0,0 +1,41 @@ 
+/* { dg-do compile { target { riscv64*-*-* } } } */
+/* { dg-options "-O2 -march=rv64gc_zicfiss -mabi=lp64d" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" } } */
+struct ad {
+  void *ae;
+};
+struct af {
+  union {
+    int *ai;
+    int *aj;
+    struct ad *ak;
+  } u;
+  struct {
+    struct {
+      long al : 1;
+      long am : 1;
+      long : 21;
+    } b;
+    long i;
+  } s;
+};
+void fdes (struct af *, void *, long *);
+
+void foo (struct af *bv, long *bw) {
+  bw[0] = bw[1] = 0;
+  if (bv->s.b.al)
+    fdes (bv, bv->u.ak->ae, bw);
+  else if (bv->s.b.am) {
+    int **p = (int**)bv->u.aj;
+    for (; *p; ++p)
+     fdes (bv, *p, bw);
+  } else
+    fdes (bv, bv->u.ai, bw);
+}
+
+/* { dg-final { scan-assembler-times "ld\tt0" 1 } } */
+/* { dg-final { scan-assembler-times "sspopchk\tt0" 1 } } */
+/* { dg-final { scan-assembler-times "jr\tt0" 1 } } */
+/* { dg-final { scan-assembler-times "ld\tra" 2 } } */
+/* { dg-final { scan-assembler-times "sspopchk\tra" 2 } } */
+/* { dg-final { scan-assembler-times "tail" 2 } } */
diff --git a/gcc/testsuite/gcc.target/riscv/ssp-2.c b/gcc/testsuite/gcc.target/riscv/ssp-2.c
new file mode 100644
index 00000000000..7c6098357e9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/ssp-2.c
@@ -0,0 +1,10 @@ 
+/* { dg-do compile { target { riscv64*-*-* } } } */
+/* { dg-options "-O0 -march=rv64gc_zicfiss -mabi=lp64d" } */
+
+void __attribute__ ((interrupt))
+foo (void)
+{
+}
+/* { dg-final { scan-assembler-times "sd\tra" 1 } } */
+/* { dg-final { scan-assembler-times "ld\tra" 1 } } */
+/* { dg-final { scan-assembler-times "sspopchk\tra" 1 } } */
diff --git a/libgcc/config/riscv/linux-unwind.h b/libgcc/config/riscv/linux-unwind.h
index 40c8f51d6fc..db755cdc6a8 100644
--- a/libgcc/config/riscv/linux-unwind.h
+++ b/libgcc/config/riscv/linux-unwind.h
@@ -19,6 +19,11 @@ 
    see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
    <http://www.gnu.org/licenses/>.  */
 
+/* Unwind shadow stack.  */
+#if defined(__riscv_zicfiss)
+# include "config/riscv/shadow-stack-unwind.h"
+#endif
+
 #ifndef inhibit_libc
 
 #include <signal.h>
diff --git a/libgcc/config/riscv/shadow-stack-unwind.h b/libgcc/config/riscv/shadow-stack-unwind.h
new file mode 100644
index 00000000000..a978a54a4ca
--- /dev/null
+++ b/libgcc/config/riscv/shadow-stack-unwind.h
@@ -0,0 +1,74 @@ 
+/* _Unwind_Frames_Extra with shadow stack.
+   Copyright (C) 2016-2025 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+<http://www.gnu.org/licenses/>.  */
+
+#define LIBGCC2_UNITS_PER_WORD (__riscv_xlen / 8)
+
+/* Unwind the shadow stack for EH.  */
+#undef _Unwind_Frames_Extra
+#define _Unwind_Frames_Extra(x)					\
+  do								\
+    {								\
+      _Unwind_Word ssp = 0;					\
+      asm volatile ("ssrdp %0" : "=r"(ssp));			\
+      if (ssp != 0)						\
+	{							\
+	  _Unwind_Word tmp = (x);				\
+	  tmp = tmp * LIBGCC2_UNITS_PER_WORD;			\
+	  while (tmp > 4096)					\
+	    {							\
+	      ssp += 4096;					\
+	      tmp -= 4096;					\
+	      asm volatile ("csrw ssp, %0" :: "r"(ssp));	\
+	      asm volatile ("sspush x5\n"			\
+			    "sspopchk x5" : : : "x5");		\
+	    }							\
+								\
+	  if (tmp > 0)						\
+	    {							\
+	      ssp += tmp;					\
+	      asm volatile ("csrw ssp, %0" :: "r"(ssp));	\
+	    }							\
+	}							\
+    }								\
+    while (0)
+
+#undef _Unwind_Frames_Increment
+#define _Unwind_Frames_Increment(exc, context, frames)	\
+    {							\
+      frames++;						\
+      if (exc->exception_class != 0			\
+	  && _Unwind_GetIP (context) != 0		\
+	  && !_Unwind_IsSignalFrame (context))		\
+	{						\
+	  _Unwind_Word ssp;				\
+	  asm volatile ("ssrdp %0" : "=r"(ssp));	\
+	  if (ssp != 0)					\
+	    {						\
+	      ssp += LIBGCC2_UNITS_PER_WORD * frames;	\
+	      _Unwind_Word ra = *(_Unwind_Word *) ssp;	\
+	      if (ra != _Unwind_GetIP (context))	\
+		return _URC_FATAL_PHASE2_ERROR;		\
+	    }						\
+	}						\
+    }