Hi!
This looks great :-)
On Wed, Apr 20, 2022 at 03:52:33PM +0200, Richard Biener wrote:
> The following mitigates a problem in combine distribute_notes which
> places an original REG_EH_REGION based on only may_trap_p which is
> good to test whether a non-call insn can possibly throw but not if
> actually it does or we care. That's something we decided at RTL
> expansion time where we possibly still know the insn evaluates
> to a constant.
>
> In fact, the REG_EH_REGION note with lp > 0 can only come from the
> original i3 and an assert is added to that effect. That means we only
> need to retain the note on i3 or, if that cannot trap, drop it but we
> should never move it to i2.
>
> For REG_EH_REGION corresponding to must-not-throw regions or
> nothrow marking try_combine gets new code ensuring we can merge
> and distribute notes which means placing must-not-throw notes
> on all result insns, and dropping nothrow notes or preserve
> them on i3 for calls.
> * combine.cc (distribute_notes): Assert that a REG_EH_REGION
> with landing pad > 0 is from i3 and only keep it there or drop
> it if the insn can not trap. Throw away REG_EH_REGION with
> landing pad = 0 or INT_MIN if it does not originate from a
> call i3. Distribute must-not-throw REG_EH_REGION to all
> resulting instructions.
> (try_combine): Ensure that we can merge REG_EH_REGION notes.
> --- a/gcc/combine.cc
> +++ b/gcc/combine.cc
> @@ -2951,6 +2951,45 @@ try_combine (rtx_insn *i3, rtx_insn *i2, rtx_insn *i1,
> rtx_insn *i0,
> return 0;
> }
>
> + /* When i3 transfers to an EH handler we cannot combine if any of the
> + sources are within a must-not-throw region. Else we can throw away
> + any nothrow, pick a random must-not-throw region or preserve the EH
> + transfer on i3. Since we want to preserve nothrow notes on calls
> + we have to avoid combining from must-not-throw stmts there as well.
> + This has to be kept in sync with distribute_note. */
> + if (rtx i3_eh = find_reg_note (i3, REG_EH_REGION, NULL_RTX))
> + {
> + int i3_lp_nr = INTVAL (XEXP (i3_eh, 0));
> + if (i3_lp_nr > 0
> + || ((i3_lp_nr == 0 || i3_lp_nr == INT_MIN) && CALL_P (i3)))
> + {
> + rtx eh;
> + int eh_lp;
> + if (((eh = find_reg_note (i2, REG_EH_REGION, NULL_RTX))
> + && (eh_lp = INTVAL (XEXP (eh, 0))) < 0
> + && eh_lp != INT_MIN)
> + || (i2
> + && (eh = find_reg_note (i2, REG_EH_REGION, NULL_RTX))
> + && (eh_lp = INTVAL (XEXP (eh, 0))) < 0
> + && eh_lp != INT_MIN)
> + || (i1
> + && (eh = find_reg_note (i1, REG_EH_REGION, NULL_RTX))
> + && (eh_lp = INTVAL (XEXP (eh, 0))) < 0
> + && eh_lp != INT_MIN)
> + || (i0
> + && (eh = find_reg_note (i0, REG_EH_REGION, NULL_RTX))
> + && (eh_lp = INTVAL (XEXP (eh, 0))) < 0
> + && eh_lp != INT_MIN))
> + {
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + fprintf (dump_file, "Can't combine insn in must-not-throw "
> + "EH region into i3 which can throws\n");
> + undo_all ();
> + return 0;
> + }
> + }
> + }
The assignments in the conditionals make this hard to read, and harder
to change, btw. A utility function wouldn't hurt? The problem of
course would be thinking of a good name for it :-)
> case REG_EH_REGION:
> - /* These notes must remain with the call or trapping instruction. */
> - if (CALL_P (i3))
> - place = i3;
> - else if (i2 && CALL_P (i2))
> - place = i2;
> - else
> - {
> - gcc_assert (cfun->can_throw_non_call_exceptions);
> - if (may_trap_p (i3))
> - place = i3;
> - else if (i2 && may_trap_p (i2))
> - place = i2;
> - /* ??? Otherwise assume we've combined things such that we
> - can now prove that the instructions can't trap. Drop the
> - note in this case. */
> - }
> - break;
> + {
> + /* This handling needs to be kept in sync with the
> + prerequesite checking in try_combine. */
(prerequisite)
> + int lp_nr = INTVAL (XEXP (note, 0));
> + /* A REG_EH_REGION note transfering control can only ever come
> + from i3 and it has to stay there. */
> + if (lp_nr > 0)
> + {
> + gcc_assert (from_insn == i3);
> + if (CALL_P (i3))
> + place = i3;
> + else
> + {
> + gcc_assert (cfun->can_throw_non_call_exceptions);
> + /* If i3 can still trap preserve the note, otherwise we've
> + combined things such that we can now prove that the
> + instructions can't trap. Drop the note in this case. */
> + if (may_trap_p (i3))
> + place = i3;
> + }
> + }
It isn't immediately clear to me that we cannot have moved a trapping
thing to i2? Do we guarantee that somewhere? Can we / should we assert
that here? gcc_assert (!(i2 && may_trap_p (i2))) or something like
that.
> + else if (lp_nr == 0 || lp_nr == INT_MIN)
> + {
> + /* nothrow notes can be dropped but we preserve
> + those on calls. */
> + if (from_insn == i3 && CALL_P (i3))
> + place = i3;
> + }
> + else
> + {
> + /* Distribute must-not-throw notes to all resulting
> + insns and just pick the first. */
> + if (!find_reg_note (i3, REG_EH_REGION, NULL_RTX)
> + && (CALL_P (i3)
> + || (cfun->can_throw_non_call_exceptions
> + && may_trap_p (i3))))
> + place = i3;
> + if (i2
> + && !find_reg_note (i2, REG_EH_REGION, NULL_RTX)
> + && cfun->can_throw_non_call_exceptions
> + && may_trap_p (i2))
> + {
> + if (place)
> + place2 = i2;
> + else
> + place = i2;
> + }
> + }
> + break;
> + }
Okay for trunk with the spello fixed, and maybe one more assert added.
Thanks for all the work on this!
Segher