Re: [PATCH v3 1/1] aarch64: Add SEH, stack unwinding and C++ exceptions.
Alice Carlotti <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
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 ...