I've also been thinking a good deal about a grand EH reorg; I'll try
to post something for you to comment on later today.

Here ya go.  Thoughts?


r~
New constructs:

  { exc_ptr, filter } = EH_LANDING_PAD;

        Placeholder for the landing-pad rtl.  Has 2 outputs
        for both the exception pointer and the filter.

  EH_DISPATCH (filter);

        Placeholder for the switch statement that we'll
        generate to jump between the alternatives for 
        different catches.

  ERT_NOP

        A new type of exception region that merely implies
        a different landing pad to the containing region.
        Inserting one of these below the "current" region
        is how we'll split critical edges, instead of copying
        regions.

  exc_ptr and filter members in each eh_region

        The code within each region will use these decls.
        When falling through from an inner region to an
        outer region, these variables must be initialized.
        These replace the EXC_PTR_EXPR and FILTER_EXPR
        constructs that we currently use.

  RESX (exc_ptr, filter);

        Of course we have this now, but we also store the
        region number in the statement directly, instead
        of via add_stmt_to_eh_region.  Which leads to special
        cases all over.  There does not seem to be a real
        point to this.  Also, we need to propagate ext_ptr
        and filter up to the next level, if we're not calling
        _Unwind_Resume.

Examples:

  Catch with nested cleanup:

        struct S { ~S(); };
        void bar();
        int x, y;
        void foo()
        {
          try {
            S s;
            bar();
          } catch (int) {
            x++;
          } catch (float) {
            y++;
          }
        }

  Compiles to (before eh_dispatch/resx expansion)

        EH Region Tree:
          3 catch int -> post-land = BB8
          4 catch float -> post-land = BB9
          2 try -> { e2, f2 }, land = BB6, post-land = BB7
            1 cleanup -> { e1, f1 }, land = BB4, post-land = BB5
          5 must_not_throw

        BB1:
          bar();                        [EH 1]
          # succ 2 4(eh)
        BB2:
          S::~S(&s);                    [EH 2]
          # succ 3
        BB3:
          return;
          # succ exit
        BB4:
          { e1, f1 } = EH_LANDING_PAD;  [EH 1]
          # succ 5
        BB5:
          S::~S(&s);                    [EH 5]
          RESX (e1, f1);
          # succ 6(eh)
        BB6:
          { e2, f2 } = EH_LANDING_PAD;  [EH 2]
          # succ 7
        BB7:
          EH_DISPATCH (f2);             [EH 2]
          # succ 8(ab) 9(ab) 10(ab)
        BB8:
          __cxa_begin_catch (e2);
          x++;
          __cxa_end_catch (e2);
          # succ 3
        BB9:
          __cxa_begin_catch (e2);
          y++;
          __cxa_end_catch (e2);
          # succ 3
        BB10:
          RESX (e2, f2);                [EH 2]

  After inlining, we have a new pass that expands both RESX and EH_DISPATCH:

        BB5:
          e2 = e1;
          f2 = f1;
          # succ 7

        BB7:
          switch (f2)
            {
            case 1:  goto BB8;
            case 2:  goto BB9;
            default: goto BB10
            }
          # succ 8 9 10

        BB10:
          _Unwind_Resume (e2);

  There are a couple of advantages that ought to be clear immediately.
  First, no more silliness that tree_empty_eh_handler_p has to clean up.
  Second, everything can be properly put into SSA form.
  Third, we're not too late to easily generate switch statements.  See

          /* ??? It is mighty inconvenient to call back into the
             switch statement generation code in expand_end_case.
             Rapid prototyping sez a sequence of ifs.  */

  which has been in except.c for nearly 10 years.

  Critical edge splitting:

        struct S { ~S() throw(); };
        void bar();
        void foo()
        {
          S s1;
          bar();
          bar();
          S s0;
          bar();
        }

  Compiles to:

        EH Region Tree:
        1 cleanup -> { e1, f1 }, land = BB7, post-land = BB8
          2 cleanup -> { e2, f2 }, land = BB5, post-land = BB6

        BB0:
          bar();                [EH 1]
          # succ 1 7(eh)
        BB1:
          bar();                [EH 1]
          # succ 2 7(eh)
        BB2:
          bar();                [EH 2]
          # succ 3 5(eh)
        BB3:
          S::~S(&s0);
          S::~S(&s1);
          # succ 4
        BB4:
          return;
        BB5:
          { e2, f2 } = EH_LANDING_PAD;
          # succ 6
        BB6:
          S::~S(&s0);
          RESX (e2, f2);
          # succ 7(eh)
        BB7:
          { e1, f1 } = EH_LANDING_PAD;
          # succ 8
        BB8:
          S::~S(&s1);
          RESX (e1, f1)

  Expand RESX and we get

        BB6:
          S::~S(&s0);
          e1 = e2;
          f1 = f2;
          # succ 8

        BB8:
          S::~S(&s1);
          _Unwind_Resume (e1);

  We decide we want to split edge BB1->BB7, so we modify:

        EH Region Tree:
        1 cleanup -> { e1, f1 }, land = BB7, post-land = BB8
          2 cleanup -> { e2, f2 }, land = BB5, post-land = BB6
          3 nop -> { e1, f1 }, land = BB9, post-land = BB8

        BB1:
          bar();                [EH 3]
          # succ 2 9(eh)

        BB9:
          { e1, f1 } = EH_LANDING_PAD;
          # succ 8

  Which, if I'm not mistaken, duplicates far less code than you
  do at present to split these edges.

  Exception optimization:

        void f();
        void g() throw();
        void h() throw();
        void foo() {
          try {
            try {
              f();
            } catch (int) {
              g();
            }
          } catch (int) {
            h();
          }
        }

  Compiles to:

        EH Region Tree:
        4 catch int
        1 try -> { e1, f1 }, land = BB6, post-land = BB7
          3 catch int
          2 try -> { e2, f2 }, land = BB2, post-land = BB3

        BB0:
          f();
          # succ 1 2(eh)                [EH 2]
        BB1:
          return;
          # succ exit
        BB2:
          { e2, f2 } = EH_LANDING_PAD;  [EH 2]
          # succ 3
        BB3:
          EH_DISPATCH (f2);             [EH 2]
          # succ 4(ab) 5(ab)
        BB4:
          __cxa_begin_catch (e2);
          g();
          __cxa_end_catch (e2);
          # succ 1
        BB5:
          RESX (e2, f2);                [EH 2]
          # succ 6(eh)
        BB6:
          { e1, f1 } = EH_LANDING_PAD;  [EH 1]
          # succ 7
        BB7:
          EH_DISPATCH (f1);             [EH 1]
          # succ 8(ab) 9(ab)
        BB8:
          __cxa_begin_catch (e1);
          h();
          __cxa_end_catch (e1);
          # succ 1
        BB9:
          RESX (e1, f1);

  Expanding RESX and EH_DISPATCH:

        BB3:
          switch (f2)
            {
            case 0:  goto BB4;
            default: goto BB5;
            }
          # succ 4 5

        BB5:
          e1 = e2;
          f1 = f2;
          # succ 7

        BB7:
          switch (f1)
            {
            case 0:  goto BB8;
            default: goto BB9;
            }
          # succ 8 9

        BB9:
          _Unwind_Resume (e1);

  See below for more discussion.

Implementation:

  EH_LANDING_PAD

        The existing code in except.c should be easily adaptable
        to expanding this gimple stmt.  All we need to do is pass
        along the two output arguments and use those instead of
        the hard-coded crtl->eh.exc_ptr and crtl->eh.filter.

  EH_DISPATCH:

        Should be expanded after inlining, and maybe before EH optimization.
        Again, the existing code within except.c can be adapted.  We
        just need to call assign_filter_values to get all the numbers
        assigned.  At which point it is trivial to build the actual
        switch.

  RESX:

        Should be expanded after inlining, and probably before EH
        optimization.  All we need to do here is either notice that
        there's no outer region and expand to _Unwind_Resume, or
        copy the exc_ptr/filter variables around and goto.

  EH optimization:

        This pass would remove shadowed exception handlers and other
        unreachable code.  It's supposed to be currently handled in
        reachable_next_level, and consists of some moderately complicated
        type checking every time we ask stmt_throws_internal.  Instead
        of having the complicated code run all the time, we'd have a
        special pass.

        (As an aside, the dead handler elimination we attempt in
        reachable_next_level doesn't work because the C++ front end
        tells us that __cxa_end_catch can throw, which is only true
        when the thrown object has a destructor that can throw.  With
        a single extra check in the front end, we can set the NOTHROW
        bit on that __cxa_end_catch call and re-enable this.)

        It could be implemented as a special form of value-range
        propagation.  We know that the runtime will only enter a
        landing pad under certain conditions, and the values that
        the FILTER variable will contain under those conditions.
        With that, it's trivial to propagate the values around.

        For instance, in the example above, we know that the only
        value that F1 and F0 will ever be assigned by EH_LANDING_PAD
        is 0.  From that we can determine that BB5 and BB9 are dead.

        If there were a cleanup handler somewhere in the middle,
        we'd have to bail, because the FILTER values for a cleanup
        can be anything (I think; but in any case it might have a
        different number as we'd generate for "int").

        We'll need something slightly different than the min/max
        values currently supported by VRP.  We'll need to track exact
        sets of values, but happily only for (generally) small numbers.

Reply via email to