On Tue, 8 Feb 2022, Michael Matz wrote:
> Hello,
>
> On Tue, 8 Feb 2022, Richard Biener wrote:
>
> > int foo(int always_true_at_runtime)
> > {
> > {
> > int *p;
> > if (always_true_at_runtime)
> > goto after;
> > lab:
> > return *p;
> > after:
> > int i = 0;
> > p = &i;
> > if (always_true_at_runtime)
> > goto lab;
> > }
> > return 0;
> > }
> >
> > For the implementation I considered starting lifetime at a DECL_EXPR
> > if it exists and otherwise at the start of the BIND_EXPR. Note the
> > complication is that control flow has to reach the lifetime-start
> > clobber before the first access. But that might also save us here,
> > since for the example above 'i' would be live via the backedge
> > from goto lab.
> >
> > That said, the existing complication is that when the gimplifier
> > visits the BIND_EXPR it has no way to know whether there will be
> > a following DECL_EXPR or not (and the gimplifier notes there are
> > cases a DECL_EXPR variable is not in a BIND_EXPR). My plan is to
> > compute this during one of the body walks the gimplifier performs
> > before gimplifying.
> >
> > Another complication is that at least the C frontend + gimplifier
> > combination for
> >
> > switch (i)
> > {
> > case 1:
> > int i;
> > break;
> > }
> >
> > will end up having the BIND_EXPR mentioning 'i' containing the
> > case label, so just placing the birth at the BIND will make it
> > unreachable as
> >
> > i = BIRTH;
> > case_label_for_1:
> > ...
>
> I think anything that needs to happen (conceptually) during the jump from
> switch to case-label needs to happen right before the jump, that might
> mean creating a new fake BLOCK that contains just the jump and the
> associated births.
>
> > conveniently at least the C frontend has a DECL_EXPR for 'i'
> > which avoids this and I did not find a way (yet) in the gimplifier
> > to rectify this when gimplifying switches.
>
> In C a 'case' label is nothing else than a normal label, it doesn't start
> it's own block or the like. So also for that one the births would need to
> be at the start of their containing blocks.
>
> > So there's still work to do but I think that starting the lifetime at a
> > DECL_EXPR if it exists is the way to go?
>
> Depends on where the DECL_EXPR is exactly placed, but probably it wouldn't
> be okay. You can't place the birth at the fall-through path, because this
> needs to work (basically your jump example above rewritten as switch):
>
> int state = 2, *p, camefrom1 = 0;
> for (;;) switch (state) {
> case 1:
> case 2: ;
> int i;
> if (state != 1) { p = &i; i = 0; }
> if (state == 1) { (*p)++; return *p; }
> state = 1;
> continue;
> }
>
> Note how i is initialized during state 2, and needs to be kept initialized
> during state 1, so there must not be a CLOBBER (birth or other) at the
> point of the declaration of 'i'. AFAICS in my simple tests a DECL_EXPR
> for 'i' is placed with the statement associated with 'case 2' label,
> putting a CLOBBER there would be the wrong thing. If the decl had an
> initializer it might be harmless, as it would be overwritten at that
> place, but even so, in this case there is no initializer. Hmm.
You get after gimplification:
state = 2;
camefrom1 = 0;
<D.1989>:
switch (state) <default: <D.1996>, case 1: <D.1983>, case 2: <D.1984>>
{
int i;
try
{
i = {CLOBBER(birth)}; /// ignore, should go away
<D.1983>:
<D.1984>:
i = {CLOBBER(birth)};
if (state != 1) goto <D.1991>; else goto <D.1992>;
<D.1991>:
p = &i;
i = 0;
<D.1992>:
if (state == 1) goto <D.1993>; else goto <D.1994>;
<D.1993>:
_1 = *p;
_2 = _1 + 1;
*p = _2;
D.1995 = *p;
// predicted unlikely by early return (on trees) predictor.
return D.1995;
<D.1994>:
state = 1;
// predicted unlikely by continue predictor.
goto <D.1987>;
}
finally
{
i = {CLOBBER(eol)};
}
}
which I think is OK? That is, when the abstract machine
arrives at 'int i;' then the previous content in 'i' goes
away? Or would
int foo()
{
goto ick;
use:
int i, *p;
return *p;
ick:
i = 1;
p = &i;
goto use;
}
require us to return 1? With the current patch 'i' is clobbered
before the return.
> Another complication arises from simple forward jumps:
>
> goto forw;
> {
> int i;
> printf("not reachable\n");
> forw:
> i = 1;
> }
>
> Despite the jump skiping the unreachable head of the BLOCK, 'i' needs to
> be considered birthed at the label. (In a way the placement of births
> exactly mirrors the placements of deaths (of course), every path from
> outside a BLOCK to inside needs to birth-clobber all variables (in C),
> like every path leaving needs to kill them. It's just that we have a
> convenient construct for the latter (try-finally), but not for the former)
The situation with an unreachable birth is handled conservatively
since we consider a variable without a (visible at RTL expansion time)
birth to conflict with all other variables.
I don't see a good way to have a birth magically appear at 'forw'
without trying to argue that the following stmt is the first mentioning
the variable.
Richard.