RFR: 8354954: Typed static memory for late initialization of static class members in Hotspot [v4]
Kim Barrett
kbarrett at openjdk.org
Sun Apr 20 08:01:50 UTC 2025
On Thu, 17 Apr 2025 18:33:36 GMT, Johan Sjölen <jsjolen at openjdk.org> wrote:
>> Hi,
>>
>> This PR introduces a `StableValue<T>` which is sized and aligned identically to a `T`, with the difference that a `StableValue<T>` needs to be explicitly instantiated.
>>
>> Dynamic static initalization in C++ leads to unpredictable bugs as there is no defined order in which objects will be initialized, to the degree that 'static initialization fiasco' is a term used. In the code I've worked on in Hotspot we resolve this by having an initialization function, and instead of having static members of `T` we have `T*` instead and use `malloc` in order to gain the memory for the objects. This is workable, but is unnecessary.
>>
>> That's why I'd like to have `StableValue<T>`. It let's you avoid the whole `malloc` thing, and we overload `->` to make it behave as if it is actually a `T`. We add in a simple checker in debug mode that checks whether the memory has been initialized before using it.
>>
>> In the code I've switched two members to be of `StableValue` instead. One is the malloc case above, the second (MemBaseline) is one where I got a bug while developing. The bug occurred because I changed the initializer of `MemBaseline` without knowing that it was dynamic-static-allocated, and the exact change I made caused weird crashes (because of initialization order issues).
>>
>> This solution is quite practical to me, but I wanted to know what others think.
>
> Johan Sjölen has updated the pull request incrementally with one additional commit since the last revision:
>
> Move semantics would be nice.
Changes requested by kbarrett (Reviewer).
src/hotspot/share/utilities/stableValue.hpp line 36:
> 34: #else
> 35: #define LAUNDER(X) (X)
> 36: #endif
Does this really need to be a macro? (Assuming we actually need it, which I'm not yet certain about.
Maybe a different approach will remove it. And I suspect `-fno-strict-aliasing` obviates it.)
src/hotspot/share/utilities/stableValue.hpp line 36:
> 34: #else
> 35: #define LAUNDER(X) (X)
> 36: #endif
Have you investigated what Visual Studio has? `std::launder` is supported in VS 2017 15.7, and the
minimum supported by JDK is well past that. The MS folks have in some cases been adding
`__builtin_XXX` that are compatible with the gcc/clang equivalents, so maybe they did that here too?
src/hotspot/share/utilities/stableValue.hpp line 41:
> 39: // May be initialized exactly once.
> 40: template<typename T>
> 41: class StableValue {
Coming in late with more bike shedding on the name. DeferredInit, DelayedInit, or something along those
lines? The idea is to emphasize that the object's construction is deferred/delayed until the call to `initialize()`.
src/hotspot/share/utilities/stableValue.hpp line 44:
> 42: union {
> 43: T _t;
> 44: };
All of this stuff about laundering and this union trick could go away if we're willing to add a pointer
to the StableValue class. Something like this (not tested):
template<typename T>
class StableValue {
alignas(T) char _data[sizeof(T)];
T* _p;
public:
StableValue() : _p(nullptr) {}
~StableValue() = default;
NONCOPYABLE(StableValue);
T* ptr() {
assert(_p != nullptr, "uninitialized");
return _p;
}
T* operator->() { return ptr(); }
T& operator*() { return *ptr(); }
template<typename... Args>
void initialize(Args&&... args) {
assert(_p == nullptr, "already initialized");
_p = ::new (_data) T(std::forward<Args>(args)...);
}
};
Regarding the destructor, quoting from the style guide: "HotSpot doesn't generally try to cleanup
on exit, and running destructors at exit can also lead to problems."
src/hotspot/share/utilities/stableValue.hpp line 57:
> 55:
> 56: T* ptr() {
> 57: return LAUNDER(&this->_t);
Do we actually need a function that potentially returns the uninitialized data? I'd prefer `ptr` and
`operator->()` were identical, and `initialize` just directly accessed `_t`. And maybe drop `ptr`.
The one external use of `ptr` that I see in the PR would be better served by adding `operator*()`.
But our code tends to use pointers more than references, so maybe it's worth retaining `ptr`, so
long as it has the is-initialized assertion too.
src/hotspot/share/utilities/stableValue.hpp line 68:
> 66: void initialize(Ts&... args) {
> 67: DEBUG_ONLY(_initialized = true);
> 68: new (ptr()) T(args...);
I think this should use perfect forwarding. The style guide should also be updated to permit using
perfect forwarding.
-------------
PR Review: https://git.openjdk.org/jdk/pull/24689#pullrequestreview-2779943132
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2051494837
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2051495512
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2051463259
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2051659153
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2051457971
PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2051457210
More information about the hotspot-dev
mailing list