2011/11/7 Rafael Espíndola <rafael.espindola at gmail.com>:> I tried a small variation: > > struct S { > static const int x; > }; > template<typename T> struct U { > static const int k; > }; > template<typename T> const int U<T>::k = T::x; > > #ifdef TU1 > extern int f(); > const int S::x = 42; > int main() { > return f() + reinterpret_cast<long>(&U<S>::k); > } > #endif > #ifdef TU2 > int f() { return U<S>::k; } > #endif > > and it crashes with gcc too :-( The original testcase works because > gcc folds the value into main even at -O0. > > If we are going to support it for real, I think Richard patch is going > on the right direction, but we should completely drop the idea of a > "weak_odr constant". If we cannot use it for instructing the linker to > put it an o ro section, the "constant" gives us nothing that the "odr" > doesn't.In cases where the C++ standard requires static initialization, introducing a write violates the guarantees of the C++ standard for static initialization. Therefore, I'm not sure the whole "make the constant writable" approach is actually viable. -Eli
On Mon, November 7, 2011 16:31, Eli Friedman wrote:> 2011/11/7 Rafael Espíndola <rafael.espindola at gmail.com>: >> I tried a small variation: >> >> >> struct S { static const int x; >> }; >> template<typename T> struct U { static const int k; >> }; >> template<typename T> const int U<T>::k = T::x; >> >> #ifdef TU1 >> extern int f(); const int S::x = 42; int main() { return f() + >> reinterpret_cast<long>(&U<S>::k); >> } >> #endif >> #ifdef TU2 >> int f() { return U<S>::k; } #endif >> >> >> and it crashes with gcc too :-( The original testcase works because gcc >> folds the value into main even at -O0.Indeed, I filed gcc bug#50968 for this last week.>> If we are going to support it for real, I think Richard patch is going >> on the right direction, but we should completely drop the idea of a "weak_odr >> constant". If we cannot use it for instructing the linker to put it an o ro >> section, the "constant" gives us nothing that the "odr" doesn't.Well, a weak_odr constant's value can be propagated in opt, which is an optimization we should strive to avoid losing. (I'm not sure whether it's a conforming optimization, though: if we end up dynamically initializing the variable, we may propagate the constant into places which are required to see its value being 0 if it's dynamically initialized).> In cases where the C++ standard requires static initialization, > introducing a write violates the guarantees of the C++ standard for static > initialization. Therefore, I'm not sure the whole "make the constant > writable" approach is actually viable.There is another problem which afflicts all solutions presented thus far, for the other kind of weak global values in C++. For a static variable defined within an inline function, we can select the variable plus guard from a TU with dynamic initialization, and select the function definition from a TU with static initialization, with the result that the object doesn't get initialized at all. I have two new proposals for fixing this, which I believe actually work. 1) [Requires ABI change] We emit dynamic initialization code for weak globals (even in TUs where static initialization is required to be performed), unless we can prove that every translation unit will use static initialization. We emit the global plus its guard variable as a single object so the linker can't separate them (this is the ABI change). If we can perform static initialization in any translation unit, then that TU emits a constant weak object (in .rodata if we want) containing the folded value and with the guard variable set to 1 (per Eli's proposal). 2) [No ABI change, but ugly and inefficient] We emit dynamic initialization code for weak globals, unless we can prove that every translation unit will use static initialization. In TUs where the value can be folded, we emit that value as a weak_odr constant. The dynamic initialization code works like this: if (((char*)guard)[0] == 0) { if (__cxa_guard_acquire(guard)) { if (object is zero-initialized) { try { ... initialize object ... } catch (...) { __cxa_guard_abort(guard); throw; } ... enqueue dtor with __cxa_atexit ... } __cxa_guard_release(guard); } } The zero-initialization check ensures that we never run both the static and dynamic initialization, unless the static initialization produces the value 0 (which is equivalent to not having performed static initialization, so is OK). One remaining issue with this is that the object still needs to be writable if we fold its initializer to 0 -- we can either emit a weak_odr global instead for such cases, or force weak_odr constants (at least ones with a zero initializer) out of .rodata. Thanks, Richard
John McCall
2011-Nov-08 08:12 UTC
[LLVMdev] [cfe-dev] weak_odr constant versus weak_odr global
On Nov 7, 2011, at 9:47 AM, Richard Smith wrote:>> In cases where the C++ standard requires static initialization, >> introducing a write violates the guarantees of the C++ standard for static >> initialization. Therefore, I'm not sure the whole "make the constant >> writable" approach is actually viable. > > There is another problem which afflicts all solutions presented thus far, for > the other kind of weak global values in C++. For a static variable defined > within an inline function, we can select the variable plus guard from a TU > with dynamic initialization, and select the function definition from a TU with > static initialization, with the result that the object doesn't get initialized > at all.> I have two new proposals for fixing this, which I believe actually work. > > 1) [Requires ABI change] We emit dynamic initialization code for weak globals > (even in TUs where static initialization is required to be performed), unless > we can prove that every translation unit will use static initialization. We > emit the global plus its guard variable as a single object so the linker can't > separate them (this is the ABI change). If we can perform static > initialization in any translation unit, then that TU emits a constant weak > object (in .rodata if we want) containing the folded value and with the guard > variable set to 1 (per Eli's proposal).The ABI actually suggests doing exactly this, except using multiple symbols linked with a COMDAT group. Unfortunately, LLVM doesn't support that COMDAT feature yet, but it could certainly be taught to. This guarantees correctness as long as every translation unit emits the code the same way, which is exactly what we'd get from an ABI change, except without actually breaking ABI conformance. Mach-O doesn't support anything like COMDAT, but the Darwin linker apparently gives significantly stronger guarantees about which object files it will take symbols from, as long as all objects have all of the symbols. John.
Hi Richard,>>> and it crashes with gcc too :-( The original testcase works because gcc >>> folds the value into main even at -O0. > > Indeed, I filed gcc bug#50968 for this last week.are you sure this is the right gcc bug number? Ciao, Duncan.
Apparently Analagous Threads
- [LLVMdev] [cfe-dev] weak_odr constant versus weak_odr global
- [LLVMdev] [cfe-dev] weak_odr constant versus weak_odr global
- [LLVMdev] [cfe-dev] weak_odr constant versus weak_odr global
- [LLVMdev] [cfe-dev] weak_odr constant versus weak_odr global
- [LLVMdev] weak_odr constant versus weak_odr global