On Wed, Jul 08, 2026 at 05:43:36PM +0200, Evgeny Karpov wrote:
> This patch reuses the existing SEH, stack unwinding and C++ exceptions
> from ix86 and implements the required unwinding for AArch64.
> 
> Co-authored-by: Zac Walker <[email protected]>
> Signed-off-by: Evgeny Karpov <[email protected]>
> 
> gcc/ChangeLog:
> 
>       * common/config/aarch64/aarch64-common.cc (aarch64_except_unwind_info):
>       Add unwinding for AArch64.
>       * config/aarch64/aarch64-abi-ms-protos.h (aarch64_pe_seh_unwind_emit):
>       Likewise.
>       * config/aarch64/aarch64-abi-ms.cc (aarch64_seh_cfa_adjust_cfa):
>       Likewise.
>       (aarch64_seh_emit_save): Likewise.
>       (aarch64_seh_emit_save_pair): Likewise.
>       (aarch64_seh_pattern_emit): Likewise.
>       (aarch64_pe_seh_unwind_emit): Likewise.
>       * config/aarch64/aarch64-abi-ms.h (CALLEE_SAVED_REG_NUMBER):
>       Define callee saved registers.
>       * config/aarch64/aarch64.cc (TARGET_SEH): Add unwinding for AArch64.
>       (aarch64_get_separate_components): Likewise.
>       (aarch64_frame_pointer_required): Likewise.
>       (aarch64_override_options_internal): Likewise.
>       (aarch64_declare_function_name): Likewise.
>       * config/aarch64/cygming.h (SYMBOL_REF_STUBVAR_P): Likewise.
>       (TARGET_SEH): Likewise.
>       (SEH_MAX_FRAME_SIZE): Likewise.
>       (TARGET_ASM_UNWIND_EMIT): Likewise.
>       (TARGET_ASM_UNWIND_EMIT_BEFORE_INSN): Likewise.
>       (TARGET_ASM_FUNCTION_END_PROLOGUE): Likewise.
>       (TARGET_ASM_EMIT_EXCEPT_PERSONALITY): Likewise.
>       (TARGET_ASM_INIT_SECTIONS): Likewise.
>       (TARGET_EXCEPT_UNWIND_INFO): Likewise.
>       (SUBTARGET_ASM_UNWIND_INIT): Likewise.
>       (ASM_DECLARE_FUNCTION_SIZE): Likewise.
>       (ASM_DECLARE_COLD_FUNCTION_SIZE): Likewise.
>       (ASM_DECLARE_COLD_FUNCTION_NAME): Likewise.
> 
> libgcc/ChangeLog:
> 
>       * unwind-seh.c (defined): Update SEH handler for AArch64.
>       (_Unwind_Backtrace): Add unwinding for AArch64.
> 
> libstdc++-v3/ChangeLog:
> 
>       * libsupc++/eh_personality.cc (defined): Add unwinding for AArch64.
> ---
>  gcc/common/config/aarch64/aarch64-common.cc |  23 ++
>  gcc/config/aarch64/aarch64-abi-ms-protos.h  |   2 +
>  gcc/config/aarch64/aarch64-abi-ms.cc        | 310 ++++++++++++++++++++
>  gcc/config/aarch64/aarch64-abi-ms.h         |   4 +
>  gcc/config/aarch64/aarch64.cc               |  17 +-
>  gcc/config/aarch64/cygming.h                |  40 ++-
>  libgcc/unwind-seh.c                         |  41 ++-
>  libstdc++-v3/libsupc++/eh_personality.cc    |  16 +
>  8 files changed, 439 insertions(+), 14 deletions(-)


The main question I have with this patch is that it doesn't currently follow
the parts of the specification that allow for unwinding from within prologues
and epilogues.  This makes me nervous for two reasons:

1. There's nothing to prevent people trying to unwind from prologues or
epilogues, so the incorrect unwind data could lead to all sorts of weird
results.  It's unclear how harmful that might be.

2. I presume that we (i.e. the whole community, not necessarily you) will
eventually implement support for unwinding in prologues and epilogues.  It
feels like the path to doing so would be easier if we can noisily ICE if we
accidentally encounter unsupported instructions and can't fix it up.  This
feels more acceptable when the status quo is "no unwind support at all", but
once the status quo includes some unwind support, then such ICEs would become
regressions.

If we get lucky and find out that the existing codegen is already compatible
with matching one unwind code to each prologue/epilogue instruction, then it
looks like it shouldn't be too much work to add that support.  However, we
won't know for sure if that's the case until we try.

If this patch were to be merged without support for unwinding within epilogues,
would you plan to add that support during this release cycle (i.e. within the
remaining 3 months of Stage 1)?


A few other general points:
- Can you add some tests please, in part to help illustrate the expected
  output.  If earlier versions had included tests, then it wouldn't have taken
  me so long to realise that you weren't outputting any epilogue unwind
  information.
- If there are unhandled instructions in the prologue or epilogue, I think it
  would be safer to ICE.  At the moment we risk emitting incorrect unwind
  information if we fail to handle a relevant instruction.
- When we're analysing instructions in aarch64_seh_pattern_emit, we should take
  into account whether we're in the prologue or an epilogue, rather than
  accepting both save and restore instructions in both contexts.



...
> +static void
> +aarch64_seh_pattern_emit (FILE *f, struct seh_frame_state *seh, const rtx 
> pat)
> +{

I didn't understand your earlier response to:

>> This function appears to check for saving a reg pair in two different ways, 
>> and
>> doesn't check for restoring a reg pair (whereas for single reg it checks both
>> save and restore).
>
>It looks like it is not reproducible for PARALLEL.

However, I now see that this doesn't occur at the moment because you're only
emitting unwind information for prologues at present.  But this makes it odd
that the code below does handle some cases of restoring registers from memory.

> +  rtx dest, src;
> +
> +  if (GET_CODE (pat) == PARALLEL)
> +    {
> +      HOST_WIDE_INT regno[2];
> +      HOST_WIDE_INT offsets[2];
> +      unsigned reg_count = 0;
> +
> +      for (unsigned i = 0, n = XVECLEN (pat, 0); i < n; ++i)
> +     {
> +       rtx ele = XVECEXP (pat, 0, i);
> +
> +       if (GET_CODE (ele) != SET)
> +         continue;
> +
> +       src = SET_SRC (ele);
> +       dest = SET_DEST (ele);
> +       if (GET_CODE (src) != REG || GET_CODE (dest) != MEM)
> +         continue;
> +
> +       dest = XEXP (dest, 0);
> +       if (dest == stack_pointer_rtx)
> +         offsets[reg_count] = 0;
> +       else if (GET_CODE (dest) == PLUS
> +               && XEXP (dest, 0) == stack_pointer_rtx)
> +         {
> +           if (GET_CODE (XEXP (dest, 1)) != CONST_INT)
> +             {
> +               sorry ("unexpected offset type");
> +               return;
> +             }
> +           offsets[reg_count] = INTVAL (XEXP (dest, 1));
> +         }
> +       else
> +         continue;
> +
> +
> +       if (reg_count == 2)
> +         gcc_unreachable ();
> +       regno[reg_count] = REGNO (src);
> +
> +       if (CALLEE_SAVED_REG_NUMBER (regno[reg_count]))
> +         ++reg_count;
> +     }
> +
> +      if (reg_count == 1)
> +     gcc_unreachable ();
> +
> +      if (reg_count == 2)
> +     {
> +       if (offsets[0] > offsets[1])
> +         {
> +           std::swap (offsets[0], offsets[1]);
> +           std::swap (regno[0], regno[1]);
> +         }
> +       if (offsets[1]- offsets[0] != UNITS_PER_WORD)
> +         gcc_unreachable ();
> +
> +       aarch64_seh_emit_save_pair (f, regno, offsets[0]);
> +     }
> +
> +      return;
> +    }
> +
> +  if (GET_CODE (pat) != SET)
> +    return;
> +
> +  src = SET_SRC (pat);
> +  dest = SET_DEST (pat);
> +  switch (GET_CODE (dest))
> +    {
> +    case REG:
> +      switch (GET_CODE (src))
> +     {
> +     case REG:
> +       if (dest == hard_frame_pointer_rtx && src == stack_pointer_rtx)
> +         fprintf (f, "\t.seh_set_fp\n");
> +       break;
> +
> +     case PLUS:
> +       if (dest == stack_pointer_rtx)
> +         {
> +           if (GET_CODE (XEXP (src, 1)) != CONST_INT)
> +             {
> +               sorry ("unexpected offset type");
> +               return;
> +             }
> +           const HOST_WIDE_INT offset = abs (INTVAL (XEXP (src, 1)));
> +           mingw_pe_seh_emit_stackalloc (f, seh, -offset);
> +         }
> +       break;
> +
> +     case MEM:
> +       src = XEXP (src, 0);
> +       if (GET_CODE (src) == PLUS
> +           && XEXP (src, 0) == stack_pointer_rtx
> +           && CALLEE_SAVED_REG_NUMBER (REGNO (dest)))
> +         {
> +           if (GET_CODE (XEXP (src, 1)) != CONST_INT)
> +             {
> +               sorry ("unexpected offset type");
> +               return;
> +             }
> +           aarch64_seh_emit_save (f, dest, INTVAL (XEXP (src, 1)));
> +         }
> +       break;
> +
> +     default:
> +       break;
> +     }
> +      break;
> +
> +    case MEM:
> +      dest = XEXP (dest, 0);
> +
> +      if (GET_CODE (dest) == PLUS && XEXP (dest, 0) == stack_pointer_rtx
> +       && GET_CODE (src) == REG && CALLEE_SAVED_REG_NUMBER (REGNO (src)))
> +     {
> +       if (GET_CODE (XEXP (dest, 1)) != CONST_INT)
> +         {
> +           sorry ("unexpected offset type");
> +           return;
> +         }
> +
> +       aarch64_seh_emit_save (f, src, INTVAL (XEXP (dest, 1)));
> +     }
> +      else if (GET_CODE (dest) == PLUS
> +            && XEXP (dest, 0) == hard_frame_pointer_rtx
> +            && GET_CODE (src) == UNSPEC
> +            && XINT (src, 1) == UNSPEC_STP)
> +     {
> +       if (GET_CODE (XEXP (dest, 1)) != CONST_INT)
> +         {
> +           sorry ("unexpected offset type");
> +           return;
> +         }
> +
> +       HOST_WIDE_INT offset = INTVAL (XEXP (dest, 1));
> +       rtvec vec = XVEC (src, 0);
> +       HOST_WIDE_INT regno[2] = { REGNO (RTVEC_ELT (vec, 0)),
> +                                  REGNO (RTVEC_ELT (vec, 1))};
> +       aarch64_seh_emit_save_pair (f, regno, offset);
> +     }
> +      break;
> +
> +    default:
> +      break;
> +    }
> +}
> +
> +/* This function looks at a single insn and emits any SEH directives
> +   required to unwind of this insn.  */
> +
> +void
> +aarch64_pe_seh_unwind_emit (FILE *out_file, rtx_insn *insn)
> +{
> +  if (!TARGET_SEH || NOTE_P (insn) || !RTX_FRAME_RELATED_P (insn))
> +    return;
> +
> +  struct seh_frame_state *seh = cfun->machine->seh;
> +  if (!seh || seh->after_prologue)
> +    return;
> +
> +  rtx pat = PATTERN (insn);
> +  for (rtx note = REG_NOTES (insn); note ; note = XEXP (note, 1))
> +    {
> +      switch (REG_NOTE_KIND (note))
> +     {
> +     case REG_FRAME_RELATED_EXPR:
> +       pat = XEXP (note, 0);
> +       break;
> +
> +     case REG_CFA_ADJUST_CFA:
> +       if (XEXP (note, 0))
> +         pat = XEXP (note, 0);
> +       if (GET_CODE (pat) == PARALLEL)
> +         pat = XVECEXP (pat, 0, 0);
> +       aarch64_seh_cfa_adjust_cfa (out_file, seh, pat);
> +       return;
> +     default:
> +       break;
> +     }
> +    }
> +
> +  aarch64_seh_pattern_emit (out_file, seh, pat);
> +}
> diff --git a/gcc/config/aarch64/aarch64-abi-ms.h 
> b/gcc/config/aarch64/aarch64-abi-ms.h
> index 1e1fb3fa400..f25e476b3a8 100644
> --- a/gcc/config/aarch64/aarch64-abi-ms.h
> +++ b/gcc/config/aarch64/aarch64-abi-ms.h
> @@ -31,6 +31,10 @@ along with GCC; see the file COPYING3.  If not see
>  #undef  STATIC_CHAIN_REGNUM
>  #define STATIC_CHAIN_REGNUM R17_REGNUM
>  
> +#define CALLEE_SAVED_REG_NUMBER(r)           \
> +  (((r) >= R19_REGNUM && (r) <= R30_REGNUM)  \
> +   || ((r) >= V8_REGNUM && (r) <= V15_REGNUM))
> +
>  #define ASM_COMMENT_START "//"
>  
>  /* ASM_OUTPUT_TYPE_DIRECTIVE is not yet supported by binutils for the
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index 5c018d92986..1687b377e83 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -124,6 +124,11 @@
>  #define TARGET_AARCH64_MS_ABI 0
>  #endif
>  
> +/* Not on SEH unless explicitly set.  */
> +#ifndef TARGET_SEH
> +#define TARGET_SEH 0
> +#endif
> +
>  /* Flags that describe how a function shares certain architectural state
>     with its callers.
>  
> @@ -9915,6 +9920,9 @@ offset_12bit_unsigned_scaled_p (machine_mode mode, 
> poly_int64 offset)
>  static sbitmap
>  aarch64_get_separate_components (void)
>  {
> +  if (TARGET_SEH)
> +     return NULL;
> +

I assume this is needed for now because combining SEH with shrink-wrapping
is more complicated and currently unimplemented?  It would be worth adding a
comment here to clarify that this is a temporary limitation that could be
removed with further work, as opposed to something inherent to the format.

I also note that the TARGET_SHRINK_WRAP_GET_SEPARATE_COMPONENTS documentation
says "Don’t define this hook if it would always return NULL", so we could
additionally make all of the TARGET_SHRINK_WRAP* macro definitions conditional
on #ifndef TARGET_SEH.

>    aarch64_frame &frame = cfun->machine->frame;
>    sbitmap components = sbitmap_alloc (LAST_SAVED_REGNUM + 1);
>    bitmap_clear (components);
> @@ -14036,7 +14044,7 @@ aarch64_frame_pointer_required ()
>  {
>    /* If the function needs to record the incoming value of PSTATE.SM,
>       make sure that the slot is accessible from the frame pointer.  */
> -  return aarch64_need_old_pstate_sm ();
> +  return TARGET_SEH || aarch64_need_old_pstate_sm ();

Is this a simplification due to not yet implementing support for unchained
functions?  In any case, the comment needs updating to reflect the added code,
and some indication of whether the restriction is temporary would be useful.

>  }
>  
>  static bool
...

Reply via email to