[PATCH] c++, dyninit: Optimize C++ dynamic initialization by constants into DECL_INITIAL adjustment [PR102876]

Jakub Jelinek jakub@redhat.com
Fri Nov 5 11:29:08 GMT 2021


On Fri, Nov 05, 2021 at 11:44:53AM +0100, Richard Biener wrote:
> Agreed that we should attack it from both sides, I just had the
> impression that most bugreports complain that clang++ can do it
> and those mostly looked opportunities that could be leveraged
> by simply const-evaluating the initializer. So I wonder if we shouldn't
> do that first.

Yes, clang++ can do it (apparently in a limited way, they can either
optimize all dynamic initializers in a TU or none, so kind of what
my patch would do without those internal functions), but they
clearly aren't doing it using const evaluating the initializer,
from -mllvm -print-after-all (seems quite unreadable variant of
GCC -fdump-{tree,ipa,rtl}-all-details with everything intermixed
on stdout) it seems to be done in a
Global Variable Optimizer
pass that seems to be before inlining but after
Interprocedural Sparse Conditional Constant Propagation
Called Value Propagation

They do seem to handle e.g.
int foo ();
int a = foo ();
int foo () { return 1; }
int bar (int);
int b = bar (foo ());
int bar (int x) { return x + 7; }
which we won't be able to optimize in the FE even if we wanted to 
treat as constexpr all functions rather than only inlines that Jason
was planning to handle like that, the bodies of
the functions aren't available when we process those variable initializers.

> All true, but at least separate functions make it easier to see what
> the initializer is without resorting to tricks like the internal functions
> you add (just guessing a bit, didn't look at the patch yet).

I think the internal function calls are actually cheaper than separate
functions and can be kept in the IL after IPA until we use them and
remove them.
If wanted, we could actually run the pass twice, once before IPA so that
it can optimize vars where early inlining optimized stuff into constants,
in that first pass we would remove the ifns wrapping only dynamic
initialization of vars that the early pass instance was able to optimize,
and then one after IPA and constant propagation, dce etc. which would
handle the rest (and that one would remove all the ifns).

> Say, if the CTOR function has
> 
>   a = 2;
>   b = foo ();
>   c = 0;
> 
> coming from
> 
> int a = baz (); // returns constant 2
> int b = foo (); // not resolvable
> int c = bar (); // returns constant 0
> 
> then how do we know that foo () does not modify a[] or c[]?
> At least modifying c from foo () should be UB?  modifying a

foo certainly can read and modify a no matter what type it has,
and it won't change anything, a has been initialized to 2 either
dynamically or statically and both behave the same.
As for c, if it is not vacuously initialized (i.e. needs construction
with non-trivial constructor), reading or storing it I believe would be
UB.  If it is vacuously initialized, then the
https://eel.is/c++draft/basic.start.static#3
I was refering to applies:
"An implementation is permitted to perform the initialization of a variable
with static or thread storage duration as a static initialization even if
such initialization is not required to be done statically, provided that

- the dynamic version of the initialization does not change the value of any
  other object of static or thread storage duration prior to its
  initialization, and

- the static version of the initialization produces the same value in the
  initialized variable as would be produced by the dynamic initialization if
  all variables not required to be initialized statically were initialized
  dynamically.

[Note 2: As a consequence, if the initialization of an object obj1 refers to
an object obj2 potentially requiring dynamic initialization and defined later
in the same translation unit, it is unspecified whether the value of obj2
used will be the value of the fully initialized obj2 (because obj2 was
statically initialized) or will be the value of obj2 merely zero-initialized.
For example, inline double fd() { return 1.0; }
extern double d1;
double d2 = d1;     // unspecified:
                    // either statically initialized to 0.0 or
                    // dynamically initialized to 0.0 if d1 is
                    // dynamically initialized, or 1.0 otherwise
double d1 = fd();   // either initialized statically or dynamically to 1.0
- end note]"

My reading is that the first bullet talks about just dynamic initialization
of the particular variable and not e.g. about all the dynamic initialization
of previous objects, so when the optimization uses those ifn markers
and checks something even stronger (that no other variables are modified
in that particular dynamic initialization) and the example shows that
at least reading of c in foo is ok but one needs to be prepared to see there
either a value that would be there if the optimization didn't happen or
one where it did.  The example doesn't talk about writing the variable...

> > For the diagnostics of UB, we have -fsanitize=address which should diagnose
> > incorrect initialization ordering.
> 
> Ah, I see.  Of course that doesn't diagnose things that are UB but
> happen to be "corrected" by link order?

It has been a while since I've looked at it, I think it works by making
the yet not constructed global vars non-accessible through shadow memory
until they are actually constructed.

	Jakub



More information about the Gcc-patches mailing list