On Thu, 21 Apr 2022, Richard Biener wrote:
> On Wed, 20 Apr 2022, Segher Boessenkool wrote:
>
> > 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 :-)
>
> I've added insn_must_not_throw_p to except.{cc,h}.
>
> > > 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)
>
> Fixed.
>
> > > + 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.
>
> IMHO asserts for catching things we don't handle are bad. I suppose
> I could add sth like
>
> @@ -3724,7 +3712,12 @@ try_combine (rtx_insn *i3, rtx_insn *i2, rtx_insn
> *i1, rt
> x_insn *i0,
> || !modified_between_p (*split, i2, i3))
> /* We can't overwrite I2DEST if its value is still used by
> NEWPAT. */
> - && ! reg_referenced_p (i2dest, newpat))
> + && ! reg_referenced_p (i2dest, newpat)
> + /* We should not split a possibly trapping part of a throwing
> + insn. */
> + && (!cfun->can_throw_non_call_exceptions
> + || !can_throw_internal (i3)
> + || !may_trap_p (*split)))
> {
> rtx newdest = i2dest;
> enum rtx_code split_code = GET_CODE (*split);
>
> but then without a testcase it's hard to see if this is to the point.
> It of course also assumes we're never going to split the call part
> from i3 to i2.
>
> It should be at least safe and with that we could add your assert,
> sligthly altered to
>
> gcc_assert (!(cfun->can_throw_non_call_exceptions
> && i2 && may_trap_p (i2)));
>
> because we have to guard against spltting say a trapping memory
> load out from a CALL_P, but only with non-call EH (the call
> might not throw but a (pre-existing!) memory load could trap).
So that doesn't work, it ICEs
FAIL: c52103k
FAIL: c52103p
FAIL: c52104p
FAIL: c95089a
FAIL: cxa4034
that means the
/* If we can split it and use I2DEST, go ahead and see if that
helps things be recognized. Verify that none of the registers
are set between I2 and I3. */
if (insn_code_number < 0
&& (split = find_split_point (&newpat, i3, false)) != 0
...
case isn't the only place we can split i2 (as seen in
distribute_notes) from i3?
This is for example
Trying 901, 902 -> 903:
901: r556:DI=0x9
902: {r555:DI=r556:DI-r132:DI;clobber flags:CC;}
REG_DEAD r556:DI
REG_DEAD r132:DI
REG_UNUSED flags:CC
REG_EQUAL 0x9-r132:DI
903: r256:QI=[r234:DI+r555:DI]
REG_DEAD r555:DI
REG_DEAD r234:DI
REG_EH_REGION 0x9
Successfully matched this instruction:
(set (reg:DI 555)
(minus:DI (reg/f:DI 234 [ arrx52.69 ])
(reg:DI 132 [ _58 ])))
Successfully matched this instruction:
(set (reg:QI 256 [ _320 ])
(mem/j:QI (plus:DI (reg:DI 555)
(const_int 9 [0x9])) [11 (*arrx52.69_259)[9]{lb: _58 sz: 1}+0
S1 A8]))
where it's probably overzealeous, and just because we have at
this point
(insn 902 901 903 134 (parallel [
(set (reg:DI 555)
(minus:DI (reg/f:DI 234 [ arrx52.69 ])
(reg:DI 132 [ _58 ])))
(clobber (reg:CC 17 flags))
]) "c52104p.adb":273:44 303 {*subdi_1}
(expr_list:REG_DEAD (reg/f:DI 234 [ arrx52.69 ])
(nil)))
and may_trap_p does
case EXPR_LIST:
/* An EXPR_LIST is used to represent a function call. This
certainly may trap. */
return 1;
so in this case for -fnon-call-exceptions we are going to reject
a lot of splits - but we actually don't (because the may_trap_p
check on the split point fails). I suppose for the assert
we'd rather want to check may_trap_p (PATTERN (i2)) then?
Do we want to do this for the other (non-call?) cases as well?
Changing the assert to
gcc_assert (!(cfun->can_throw_non_call_exceptions
&& i2 && may_trap_p (PATTERN (i2))));
fixes the ICE (just checked one of the testcases).
I'm going to re-check with this, but given may_trap_p is looking
to err on the trapping side too easily maybe an assert isn't a good
idea.
Richard.