RFR: 8354954: Typed static memory for late initialization of static class members in Hotspot [v4]

Quan Anh Mai qamai at openjdk.org
Tue Apr 22 05:01:06 UTC 2025


On Tue, 22 Apr 2025 04:52:45 GMT, Quan Anh Mai <qamai at openjdk.org> wrote:

>> I think something this works, and that no laundering is required.
>> 
>> template<typename T>
>> class StableValue {
>>   union { T _obj; };
>>   DEBUG_ONLY(bool _initialized;)
>> public:
>>   StableValue() DEBUG_ONLY(: _initialized(false)) {}
>>   ~StableValue() {}
>>   NONCOPYABLE(StableValue);
>>   T* ptr() {
>>     assert(_initialized, "uninitialized");
>>     return &_obj;
>>   }
>>   T* operator->() { return ptr(); }
>>   T& operator*() { return *ptr(); }
>>   template<typename... Args>
>>   void initialize(Args&&... args) {
>>     assert(!_initialized, "already initialized");
>>     using NCVP = std::add_pointer_t<std::remove_cv_t<T>>;
>>     ::new (const_cast<NCVP>(_obj)) T(std::forward<Args>(args)...);
>>   }
>> };
>> 
>> [Note that I've made a few other improvements, such as supporting a cv-qualified
>> type and being non-copyable.  Obviously there's lots of missing comments...]
>> 
>> The rationale for not needing laundering is as follows.
>> 
>> std::launder is introduced to work around some restrictions involving reuse of
>> storage for a different object.  Specifically, C++17 6.8.8 (Object lifetime)
>> says
>> 
>> --- begin quote ---
>> 
>> If, after the lifetime of an object has ended and before the storage which the
>> object occupied is reused or released, a new object is created at the storage
>> location which the original object occupied, a pointer that pointed to the
>> original object, a reference that referred to the original object, or the name
>> of the original object will automatically refer to the new object and, once
>> the lifetime of the new object has started, can be used to manipulate the new
>> object, if:
>> 
>> [... list of requirements ...]
>> 
>> [Note: Note: If these conditions are not met, a pointer to the new object can
>> be obtained from a pointer that represents the address of its storage by
>> calling std::launder. -- end note.]
>> 
>> --- end quote ---
>> 
>> Other than it's definition and an example, that's the only occurrence of
>> "launder" in C++17.
>> 
>> But if we're careful, that section doesn't apply at all.  By using the
>> uninitialized anonymous union trick (or a similarly uninitialized and
>> appropriately aligned and sized char array), we have storage but not an object
>> whose lifetime has ended.  There wasn't a prior object in that storage.
>> 
>> Instead we're dealing with storage either before or after the object's
>> lifetime has started. There are restrictions on what one can do with a pointer
>> to storage before the lifetime of the object starts, b...
>
> I believe you are right, we do not need launder here. From [[class.union]/6](https://eel.is/c++draft/class.union#6):
> 
>> [Note [5](https://eel.is/c++draft/class.union#general-note-5): In cases where the above rule does not apply, the active member of a union can only be changed by the use of a placement [new-expression](https://eel.is/c++draft/expr.new#nt:new-expression)[.](https://eel.is/c++draft/class.union#general-6.sentence-1) — end note]
> [Example [3](https://eel.is/c++draft/class.union#general-example-3): Consider an object u of a union type U having non-static data members m of type M and n of type N[.](https://eel.is/c++draft/class.union#general-6.sentence-2) If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement [new-expression](https://eel.is/c++draft/expr.new#nt:new-expression) as follows:
> u.m.~M();
> new (&u.n) N;
> — end example]
> 
> We can see that `::new(_&obj) T()` is considered an act to make `_obj` active, not to create a new `T`, the `_obj` itself has been created before at this location, it is just that it has not been initialized and its lifetime has not started. As a result, `_obj` can be used to refer to this object without launder.

To be perfectly clear, we can just remove launder from the patch, and do not need to do this:

    template<typename... Args>
    void initialize(Args&&... args) {
      assert(!_initialized, "already initialized");
      using NCVP = std::add_pointer_t<std::remove_cv_t<T>>;
      ::new (const_cast<NCVP>(_obj)) T(std::forward<Args>(args)...);
    }

Just do it normal:

    template<typename... Args>
    void initialize(Args&&... args) {
      assert(!_initialized, "already initialized");
      ::new (&_obj) T(std::forward<Args>(args)...);
    }

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/24689#discussion_r2053330459


More information about the hotspot-dev mailing list