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