On Thu, Jun 25, 2026 at 07:41:49PM +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]>
A general remark: there are several places in this patch in which you detect an
irrelevant instruction in the prologue/epilogue and don't emit a directive.
This seems wrong to me, because the unwind codes should map 1:1 to the
instructions in a prologue or epilogue, so I would expect .seh_nop to be
emitted for any irrelevant instructions in the middle of the prologue/epilogue.
...
> diff --git a/gcc/config/aarch64/aarch64-abi-ms-protos.h
> b/gcc/config/aarch64/aarch64-abi-ms-protos.h
> index f08ea29d0c2..29e82ae392e 100644
> --- a/gcc/config/aarch64/aarch64-abi-ms-protos.h
> +++ b/gcc/config/aarch64/aarch64-abi-ms-protos.h
> @@ -28,6 +28,8 @@ extern tree aarch64_ms_variadic_abi_fn_abi_va_list (tree
> fndecl);
>
> extern tree aarch64_ms_variadic_abi_canonical_va_list_type (tree type);
>
> +extern void aarch64_pe_seh_unwind_emit (FILE *, rtx_insn *);
> +
> extern int aarch64_arg_partial_bytes (cumulative_args_t,
> const function_arg_info &);
V>
> diff --git a/gcc/config/aarch64/aarch64-abi-ms.cc
> b/gcc/config/aarch64/aarch64-abi-ms.cc
> index 1eaab4246d3..d4db95868cb 100644
> --- a/gcc/config/aarch64/aarch64-abi-ms.cc
> +++ b/gcc/config/aarch64/aarch64-abi-ms.cc
> @@ -32,6 +32,7 @@
> #include "regs.h"
> #include "function-abi.h"
> #include "builtins.h"
> +#include "diagnostic-core.h"
> #include "aarch64-abi-ms-protos.h"
>
> /* Iterate through the target-specific builtin types for va_list.
> @@ -104,3 +105,312 @@ aarch64_arg_partial_bytes (cumulative_args_t pcum_v,
>
> return 0;
> }
> +
> +/* Emit SEH directive to adjust the stack pointer by offset. */
> +
> +static void
> +aarch64_seh_cfa_adjust_cfa (FILE *f, struct seh_frame_state *seh, rtx pat)
> +{
> + rtx src = SET_SRC (pat);
> + if (GET_CODE (src) != PLUS)
> + gcc_unreachable ();
> +
> + const HOST_WIDE_INT reg_offset = INTVAL (XEXP (src, 1));
> + src = XEXP (src, 0);
> +
> + gcc_assert (src == stack_pointer_rtx);
> + gcc_assert (seh->cfa_reg == stack_pointer_rtx);
> +
> + rtx dest = SET_DEST (pat);
> + if (REGNO (dest) != STACK_POINTER_REGNUM)
> + gcc_unreachable ();
> +
> + mingw_pe_seh_emit_stackalloc (f, seh, reg_offset);
> +}
> +
> +/* Emit SEH directive to save a REG. */
> +
> +static void
> +aarch64_seh_emit_save (FILE *f, rtx reg, HOST_WIDE_INT cfa_offset)
> +{
> + const unsigned int regno = REGNO (reg);
> + fputs ((FP_REGNUM_P (regno) ? " \t.seh_save_freg\t"
> + : GP_REGNUM_P (regno) ? " \t.seh_save_reg\t"
> + : (gcc_unreachable (), "")), f);
> + if (REGNO (reg) >= V0_REGNUM)
> + fprintf (f, "d%d", REGNO (reg) - V0_REGNUM);
> + else
> + fprintf (f, "x%d", REGNO (reg));
> + fprintf (f, ", " HOST_WIDE_INT_PRINT_DEC " \n", abs (cfa_offset));
> + return;
> +}
> +
> +/* Emit SEH directive to save REG or REG pair. */
> +
> +static void
> +aarch64_seh_emit_save_pair (FILE *f, HOST_WIDE_INT regno[2],
> + HOST_WIDE_INT offset)
> +{
> + const char *unwind_code_prefix = ".seh_save_";
> + const char *unwind_code_infix = "";
> + bool negative_offset = offset < 0;
> + const char *unwind_code_suffix = negative_offset ? "_x": "";
> + offset = abs (offset);
> +
> + if (regno[0] == FP_REGNUM && regno[1] == LR_REGNUM)
> + {
> + unwind_code_infix = "fplr";
> + fprintf (f, "\t%s%s%s\t%ld\n",
> + unwind_code_prefix,
> + unwind_code_infix,
> + unwind_code_suffix,
> + offset);
> + return;
> + }
> +
> + const unsigned save_r19r20_x_max_offset = 248;
> + if (regno[0] == R19_REGNUM && regno[1] == R20_REGNUM && negative_offset
> + && offset <= save_r19r20_x_max_offset)
> + {
> + unwind_code_infix = "r19r20";
> + fprintf (f, "\t%s%s%s\t%ld\n",
> + unwind_code_prefix,
> + unwind_code_infix,
> + unwind_code_suffix,
> + offset);
> + return;
> + }
> +
> + bool callee_save_reg1 = CALLEE_SAVED_REG_NUMBER (regno[0]);
> + bool callee_save_reg2 = CALLEE_SAVED_REG_NUMBER (regno[1]);
> +
> + if (!callee_save_reg1 && !callee_save_reg2)
> + return;
> +
> + bool emit_single_register = false;
> + unsigned reg_count = 1;
> + if (callee_save_reg1 && callee_save_reg2)
> + {
> + emit_single_register = (regno[1] - regno[0]) != 1;
If the instruction saves two non-consecutive callee-saved registers, then this
would silently ignore one of them in the unwind data. Since we can't represent
such an instruction accurately with unwind codes, we should try to ensure it
never arises in a prologue or epilogue (perhaps we already do this), and I
think we should either sorry or ice if we do encounter it to avoid silent
wrong-code issues.
> + reg_count += emit_single_register ? 1: 0;
> + }
> + else
> + {
> + emit_single_register = true;
> + if (!callee_save_reg1)
> + regno[0] = regno[1];
> + }
> +
> + unwind_code_infix = emit_single_register ? "reg": "regp";
> + char reg_prefix = 'x';
> + unsigned regno_offset = 0;
> + HOST_WIDE_INT offset_increment = 8;
> +
> + if (FP_REGNUM_P (regno[0]))
> + {
> + unwind_code_prefix = ".seh_save_f";
> + reg_prefix = 'd';
> + regno_offset = V0_REGNUM;
> + }
> +
> + for (unsigned i = 0; i < reg_count; ++i)
> + fprintf (f, "\t%s%s%s\t%c%ld, %ld\n",
> + unwind_code_prefix,
> + unwind_code_infix,
> + unwind_code_suffix,
> + reg_prefix,
> + regno[i] - regno_offset,
> + offset + i * offset_increment);
> +}
> +
> +/* Emmit SEH directive if the pattern is recognized. */
Typo: Emit.
> +
> +static void
> +aarch64_seh_pattern_emit (FILE *f, struct seh_frame_state *seh, const rtx
> pat)
> +{
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).
> + 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;
> + }
> +}
> +