diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 1281f1be6a6..884e168949e 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -5518,7 +5518,8 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, vk->set_non_atomic_size_in_bytes(_layout_info->_non_atomic_size_in_bytes); vk->set_non_atomic_alignment(_layout_info->_non_atomic_alignment); vk->set_atomic_size_in_bytes(_layout_info->_atomic_layout_size_in_bytes); - vk->set_nullable_size_in_bytes(_layout_info->_nullable_layout_size_in_bytes); + vk->set_nullable_atomic_size_in_bytes(_layout_info->_nullable_atomic_layout_size_in_bytes); + vk->set_nullable_non_atomic_size_in_bytes(_layout_info->_nullable_non_atomic_layout_size_in_bytes); vk->set_null_marker_offset(_layout_info->_null_marker_offset); vk->set_null_reset_value_offset(_layout_info->_null_reset_value_offset); if (_layout_info->_is_empty_inline_klass) vk->set_is_empty_inline_type(); diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp index 0179f189bef..995699d5ded 100644 --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -83,6 +83,8 @@ class FieldLayoutInfo : public ResourceObj { int _non_atomic_size_in_bytes; int _non_atomic_alignment; int _atomic_layout_size_in_bytes; + int _nullable_atomic_layout_size_in_bytes; + int _nullable_non_atomic_layout_size_in_bytes; int _nullable_layout_size_in_bytes; int _null_marker_offset; int _null_reset_value_offset; diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index a7550f94641..d5b399681f6 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -38,7 +38,12 @@ #include "utilities/powerOfTwo.hpp" static LayoutKind field_layout_selection(FieldInfo field_info, Array* inline_layout_info_array, - bool use_atomic_flat) { + bool can_use_atomic_flat) { + + // The can_use_atomic_flat argument indicates if an atomic flat layout can be used for this field. + // This argument will be false if the container is a loosely consistent value class. Using an atomic layout + // in a container that has no atomicity guarantee creates a risk to see this field's value be subject to + // tearing even if the field's class was declared atomic (non loosely consistent). if (!UseFieldFlattening) { return LayoutKind::REFERENCE; @@ -66,13 +71,23 @@ static LayoutKind field_layout_selection(FieldInfo field_info, Arraymust_be_atomic() || AlwaysAtomicAccesses) { if (vk->is_naturally_atomic() && vk->has_non_atomic_layout()) return LayoutKind::NULL_FREE_NON_ATOMIC_FLAT; - return (vk->has_atomic_layout() && use_atomic_flat) ? LayoutKind::NULL_FREE_ATOMIC_FLAT : LayoutKind::REFERENCE; + return (vk->has_atomic_layout() && can_use_atomic_flat) ? LayoutKind::NULL_FREE_ATOMIC_FLAT : LayoutKind::REFERENCE; } else { return vk->has_non_atomic_layout() ? LayoutKind::NULL_FREE_NON_ATOMIC_FLAT : LayoutKind::REFERENCE; } } else { + // To preserve the consistency between the null-marker and the field content, the NULLABLE_NON_ATOMIC_FLAT + // can only be used in containers that have atomicity quarantees (can_use_atomic_flat argument set to true) + if (field_info.access_flags().is_strict() && field_info.access_flags().is_final() && can_use_atomic_flat) { + if (vk->has_nullable_non_atomic_layout()) return LayoutKind::NULLABLE_NON_ATOMIC_FLAT; + } + // Another special case where NULLABLE_NON_ATOMIC_FLAT can be used: nullable empty values, because the + // payload of those values contains only the null-marker + if (vk->is_empty_inline_type() && vk->has_nullable_non_atomic_layout()) { + return LayoutKind::NULLABLE_NON_ATOMIC_FLAT; + } if (UseNullableValueFlattening && vk->has_nullable_atomic_layout()) { - return use_atomic_flat ? LayoutKind::NULLABLE_ATOMIC_FLAT : LayoutKind::REFERENCE; + return can_use_atomic_flat ? LayoutKind::NULLABLE_ATOMIC_FLAT : LayoutKind::REFERENCE; } else { return LayoutKind::REFERENCE; } @@ -92,7 +107,11 @@ static void get_size_and_alignment(InlineKlass* vk, LayoutKind kind, int* size, case LayoutKind::NULLABLE_ATOMIC_FLAT: *size = vk->nullable_atomic_size_in_bytes(); *alignment = *size; - break; + break; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + *size = vk->nullable_non_atomic_size_in_bytes(); + *alignment = vk->non_atomic_alignment(); + break; default: ShouldNotReachHere(); } @@ -736,7 +755,8 @@ FieldLayoutBuilder::FieldLayoutBuilder(const Symbol* classname, ClassLoaderData* _non_atomic_layout_size_in_bytes(-1), _non_atomic_layout_alignment(-1), _atomic_layout_size_in_bytes(-1), - _nullable_layout_size_in_bytes(-1), + _nullable_atomic_layout_size_in_bytes(-1), + _nullable_non_atomic_layout_size_in_bytes(-1), _fields_size_sum(0), _declared_non_static_fields_count(0), _has_non_naturally_atomic_fields(false), @@ -1139,10 +1159,9 @@ void FieldLayoutBuilder::compute_inline_class_layout() { } } - // Next step is the nullable layout: the layout must include a null marker and must also be atomic - if (UseNullableValueFlattening) { + // Next step is the nullable layouts: they must include a null marker + if (UseNullableValueFlattening || UseNullableNonAtomicValueFlattening) { // Looking if there's an empty slot inside the layout that could be used to store a null marker - // FIXME: could it be possible to re-use the .empty field as a null marker for empty values? LayoutRawBlock* b = _layout->first_field_block(); assert(b != nullptr, "A concrete value class must have at least one (possible dummy) field"); int null_marker_offset = -1; @@ -1171,20 +1190,28 @@ void FieldLayoutBuilder::compute_inline_class_layout() { null_marker_offset = marker->offset(); } } - - // Now that the null marker is there, the size of the nullable layout must computed (remember, must be atomic too) + assert(null_marker_offset != -1, "Sanity check"); + // Now that the null marker is there, the size of the nullable layout must computed int new_raw_size = _layout->last_block()->offset() - _layout->first_field_block()->offset(); - int nullable_size = round_up_power_of_2(new_raw_size); - if (nullable_size <= (int)MAX_ATOMIC_OP_SIZE) { - _nullable_layout_size_in_bytes = nullable_size; + if (UseNullableNonAtomicValueFlattening) { + _nullable_non_atomic_layout_size_in_bytes = new_raw_size; _null_marker_offset = null_marker_offset; - } else { + _non_atomic_layout_alignment = _payload_alignment; + } + if (UseNullableValueFlattening) { + // For the nullable atomic layout, the size mut be compatible with the platform capabilities + int nullable_atomic_size = round_up_power_of_2(new_raw_size); + if (nullable_atomic_size <= (int)MAX_ATOMIC_OP_SIZE) { + _nullable_atomic_layout_size_in_bytes = nullable_atomic_size; + _null_marker_offset = null_marker_offset; + } + } + if (_null_marker_offset == -1) { // No nullable layout has been accepted // If the nullable layout is rejected, the NULL_MARKER block should be removed // from the layout, otherwise it will appear anyway if the layout is printer if (!_is_empty_inline_class) { // empty values don't have a dedicated NULL_MARKER block _layout->remove_null_marker(); } - _null_marker_offset = -1; } } // If the inline class has an atomic or nullable (which is also atomic) layout, @@ -1207,18 +1234,18 @@ void FieldLayoutBuilder::compute_inline_class_layout() { assert(_layout->first_field_block() != nullptr, "A concrete value class must have at least one (possible dummy) field"); _layout->shift_fields(shift); _payload_offset = _layout->first_field_block()->offset(); - if (has_nullable_atomic_layout()) { + if (has_nullable_atomic_layout() || has_nullable_non_atomic_layout()) { assert(!_is_empty_inline_class, "Should not get here with empty values"); _null_marker_offset = _layout->find_null_marker()->offset(); } _payload_alignment = required_alignment; } else { _atomic_layout_size_in_bytes = -1; - if (has_nullable_atomic_layout() && !_is_empty_inline_class) { // empty values don't have a dedicated NULL_MARKER block + if (has_nullable_atomic_layout() && !has_nullable_non_atomic_layout() && !_is_empty_inline_class) { // empty values don't have a dedicated NULL_MARKER block _layout->remove_null_marker(); + _null_marker_offset = -1; } - _nullable_layout_size_in_bytes = -1; - _null_marker_offset = -1; + _nullable_atomic_layout_size_in_bytes = -1; } } else { _payload_alignment = required_alignment; @@ -1472,7 +1499,8 @@ void FieldLayoutBuilder::epilogue() { _info->_non_atomic_size_in_bytes = _non_atomic_layout_size_in_bytes; _info->_non_atomic_alignment = _non_atomic_layout_alignment; _info->_atomic_layout_size_in_bytes = _atomic_layout_size_in_bytes; - _info->_nullable_layout_size_in_bytes = _nullable_layout_size_in_bytes; + _info->_nullable_atomic_layout_size_in_bytes = _nullable_atomic_layout_size_in_bytes; + _info->_nullable_non_atomic_layout_size_in_bytes = _nullable_non_atomic_layout_size_in_bytes; _info->_null_marker_offset = _null_marker_offset; _info->_null_reset_value_offset = _static_layout->null_reset_value_offset(); _info->_is_empty_inline_klass = _is_empty_inline_class; @@ -1560,11 +1588,19 @@ void FieldLayoutBuilder::epilogue() { if (has_nullable_atomic_layout()) { st.print_cr("%s layout: %d/%d", LayoutKindHelper::layout_kind_as_string(LayoutKind::NULLABLE_ATOMIC_FLAT), - _nullable_layout_size_in_bytes, _nullable_layout_size_in_bytes); + _nullable_atomic_layout_size_in_bytes, _nullable_atomic_layout_size_in_bytes); } else { st.print_cr("%s layout: -/-", LayoutKindHelper::layout_kind_as_string(LayoutKind::NULLABLE_ATOMIC_FLAT)); } + if (has_nullable_non_atomic_layout()) { + st.print_cr("%s layout: %d/%d", + LayoutKindHelper::layout_kind_as_string(LayoutKind::NULLABLE_NON_ATOMIC_FLAT), + _nullable_non_atomic_layout_size_in_bytes, _non_atomic_layout_alignment); + } else { + st.print_cr("%s layout: -/-", + LayoutKindHelper::layout_kind_as_string(LayoutKind::NULLABLE_NON_ATOMIC_FLAT)); + } if (_null_marker_offset != -1) { st.print_cr("Null marker offset = %d", _null_marker_offset); } diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp index f47cab8f777..ae412dbbd3c 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp @@ -296,7 +296,8 @@ class FieldLayoutBuilder : public ResourceObj { int _non_atomic_layout_size_in_bytes; int _non_atomic_layout_alignment; int _atomic_layout_size_in_bytes; - int _nullable_layout_size_in_bytes; + int _nullable_atomic_layout_size_in_bytes; + int _nullable_non_atomic_layout_size_in_bytes; int _fields_size_sum; int _declared_non_static_fields_count; bool _has_non_naturally_atomic_fields; @@ -318,7 +319,7 @@ class FieldLayoutBuilder : public ResourceObj { GrowableArray* field_info, bool is_contended, bool is_inline_type, bool is_abstract_value, bool must_be_atomic, FieldLayoutInfo* info, Array* inline_layout_info_array); - int payload_offset() const { assert(_payload_offset != -1, "Uninitialized"); return _payload_offset; } + int payload_offset() const { assert(_payload_offset != -1, "Uninitialized"); return _payload_offset; } int payload_layout_size_in_bytes() const { return _payload_size_in_bytes; } int payload_layout_alignment() const { assert(_payload_alignment != -1, "Uninitialized"); return _payload_alignment; } bool has_non_atomic_flat_layout() const { return _non_atomic_layout_size_in_bytes != -1; } @@ -326,8 +327,10 @@ class FieldLayoutBuilder : public ResourceObj { int non_atomic_layout_alignment() const { return _non_atomic_layout_alignment; } bool has_atomic_layout() const { return _atomic_layout_size_in_bytes != -1; } int atomic_layout_size_in_bytes() const { return _atomic_layout_size_in_bytes; } - bool has_nullable_atomic_layout() const { return _nullable_layout_size_in_bytes != -1; } - int nullable_layout_size_in_bytes() const { return _nullable_layout_size_in_bytes; } + bool has_nullable_atomic_layout() const { return _nullable_atomic_layout_size_in_bytes != -1; } + int nullable_layout_size_in_bytes() const { return _nullable_atomic_layout_size_in_bytes; } + bool has_nullable_non_atomic_layout() const { return _nullable_non_atomic_layout_size_in_bytes != -1; } + int nullable_non_atomic_layout_size_in_bytes() const { return _nullable_non_atomic_layout_size_in_bytes; } int null_marker_offset() const { return _null_marker_offset; } bool is_empty_inline_class() const { return _is_empty_inline_class; } diff --git a/src/hotspot/share/oops/flatArrayKlass.cpp b/src/hotspot/share/oops/flatArrayKlass.cpp index d6d73d9665a..b9551b88747 100644 --- a/src/hotspot/share/oops/flatArrayKlass.cpp +++ b/src/hotspot/share/oops/flatArrayKlass.cpp @@ -57,6 +57,7 @@ FlatArrayKlass::FlatArrayKlass(Klass* element_klass, Symbol* name, ArrayProperties props, LayoutKind lk) : ObjArrayKlass(1, element_klass, name, Kind, props, markWord::flat_array_prototype(lk)) { assert(element_klass->is_inline_klass(), "Expected Inline"); + assert(lk != LayoutKind::NULLABLE_NON_ATOMIC_FLAT, "Layout not supported by arrays yet (needs frozen arrays)"); assert(LayoutKindHelper::is_flat(lk), "Must be a flat layout"); set_element_klass(InlineKlass::cast(element_klass)); @@ -82,6 +83,8 @@ FlatArrayKlass::FlatArrayKlass(Klass* element_klass, Symbol* name, ArrayProperti assert(!layout_helper_is_null_free(layout_helper()), "Must be"); assert(!prototype_header().is_null_free_array(), "Must be"); break; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + ShouldNotReachHere(); default: ShouldNotReachHere(); break; diff --git a/src/hotspot/share/oops/inlineKlass.cpp b/src/hotspot/share/oops/inlineKlass.cpp index 7ed17ae84ae..5023e246ec7 100644 --- a/src/hotspot/share/oops/inlineKlass.cpp +++ b/src/hotspot/share/oops/inlineKlass.cpp @@ -80,7 +80,7 @@ void InlineKlass::init_fixed_block() { set_non_atomic_size_in_bytes(-1); set_non_atomic_alignment(-1); set_atomic_size_in_bytes(-1); - set_nullable_size_in_bytes(-1); + set_nullable_atomic_size_in_bytes(-1); set_null_marker_offset(-1); } @@ -134,6 +134,10 @@ int InlineKlass::layout_size_in_bytes(LayoutKind kind) const { assert(has_nullable_atomic_layout(), "Layout not available"); return nullable_atomic_size_in_bytes(); break; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + assert(has_nullable_non_atomic_layout(), "Layout not available"); + return nullable_non_atomic_size_in_bytes(); + break; case LayoutKind::BUFFERED: return payload_size_in_bytes(); break; @@ -156,6 +160,10 @@ int InlineKlass::layout_alignment(LayoutKind kind) const { assert(has_nullable_atomic_layout(), "Layout not available"); return nullable_atomic_size_in_bytes(); break; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + assert(has_nullable_non_atomic_layout(), "Layout not available"); + return non_atomic_alignment(); + break; case LayoutKind::BUFFERED: return payload_alignment(); break; @@ -175,6 +183,9 @@ bool InlineKlass::is_layout_supported(LayoutKind lk) { case LayoutKind::NULLABLE_ATOMIC_FLAT: return has_nullable_atomic_layout(); break; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + return has_nullable_non_atomic_layout(); + break; case LayoutKind::BUFFERED: return true; break; @@ -187,8 +198,9 @@ void InlineKlass::copy_payload_to_addr(void* src, void* dst, LayoutKind lk, bool assert(is_layout_supported(lk), "Unsupported layout"); assert(lk != LayoutKind::REFERENCE && lk != LayoutKind::UNKNOWN, "Sanity check"); switch(lk) { + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: case LayoutKind::NULLABLE_ATOMIC_FLAT: { - if (is_payload_marked_as_null((address)src)) { + if (is_payload_marked_as_null((address)src)) { if (!contains_oops()) { mark_payload_as_null((address)dst); return; @@ -230,6 +242,7 @@ oop InlineKlass::read_payload_from_addr(const oop src, size_t offset, LayoutKind assert(src != nullptr, "Must be"); assert(is_layout_supported(lk), "Unsupported layout"); switch(lk) { + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: case LayoutKind::NULLABLE_ATOMIC_FLAT: { if (is_payload_marked_as_null((address)((char*)(oopDesc*)src + offset))) { return nullptr; diff --git a/src/hotspot/share/oops/inlineKlass.hpp b/src/hotspot/share/oops/inlineKlass.hpp index 461c015987c..6894e225fac 100644 --- a/src/hotspot/share/oops/inlineKlass.hpp +++ b/src/hotspot/share/oops/inlineKlass.hpp @@ -113,7 +113,12 @@ class InlineKlass: public InstanceKlass { address adr_nullable_atomic_size_in_bytes() const { assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); - return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _nullable_size_in_bytes)); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _nullable_atomic_size_in_bytes)); + } + + address adr_nullable_non_atomic_size_in_bytes() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _nullable_non_atomic_size_in_bytes)); } address adr_null_marker_offset() const { @@ -152,23 +157,26 @@ class InlineKlass: public InstanceKlass { bool has_nullable_atomic_layout() const { return nullable_atomic_size_in_bytes() != -1; } int nullable_atomic_size_in_bytes() const { return *(int*)adr_nullable_atomic_size_in_bytes(); } - void set_nullable_size_in_bytes(int size) { *(int*)adr_nullable_atomic_size_in_bytes() = size; } + void set_nullable_atomic_size_in_bytes(int size) { *(int*)adr_nullable_atomic_size_in_bytes() = size; } + bool has_nullable_non_atomic_layout() const { return nullable_non_atomic_size_in_bytes() != -1; } + int nullable_non_atomic_size_in_bytes() const { return *(int*)adr_nullable_non_atomic_size_in_bytes(); } + void set_nullable_non_atomic_size_in_bytes(int size) { *(int*)adr_nullable_non_atomic_size_in_bytes() = size; } int null_marker_offset() const { return *(int*)adr_null_marker_offset(); } int null_marker_offset_in_payload() const { return null_marker_offset() - payload_offset(); } void set_null_marker_offset(int offset) { *(int*)adr_null_marker_offset() = offset; } bool is_payload_marked_as_null(address payload) { - assert(has_nullable_atomic_layout(), " Must have"); + assert(has_nullable_atomic_layout() || has_nullable_non_atomic_layout(), " Must have"); return *((jbyte*)payload + null_marker_offset_in_payload()) == 0; } void mark_payload_as_non_null(address payload) { - assert(has_nullable_atomic_layout(), " Must have"); + assert(has_nullable_atomic_layout() || has_nullable_non_atomic_layout(), " Must have"); *((jbyte*)payload + null_marker_offset_in_payload()) = 1; } void mark_payload_as_null(address payload) { - assert(has_nullable_atomic_layout(), " Must have"); + assert(has_nullable_atomic_layout() || has_nullable_non_atomic_layout(), " Must have"); *((jbyte*)payload + null_marker_offset_in_payload()) = 0; } diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 4f2a6c4479c..d2157859400 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -153,7 +153,8 @@ class InlineKlassFixedBlock { int _non_atomic_size_in_bytes; // size of null-free non-atomic flat layout int _non_atomic_alignment; // alignment requirement for null-free non-atomic layout int _atomic_size_in_bytes; // size and alignment requirement for a null-free atomic layout, -1 if no atomic flat layout is possible - int _nullable_size_in_bytes; // size and alignment requirement for a nullable layout (always atomic), -1 if no nullable flat layout is possible + int _nullable_atomic_size_in_bytes; // size and alignment requirement for a nullable atomic layout, -1 if not available + int _nullable_non_atomic_size_in_bytes; // size and alignment requirement for a nullable non-atomic layout, -1 if not available int _null_marker_offset; // expressed as an offset from the beginning of the object for a heap buffered value // payload_offset must be subtracted to get the offset from the beginning of the payload diff --git a/src/hotspot/share/oops/layoutKind.cpp b/src/hotspot/share/oops/layoutKind.cpp index a9a22162e79..eaae6978328 100644 --- a/src/hotspot/share/oops/layoutKind.cpp +++ b/src/hotspot/share/oops/layoutKind.cpp @@ -36,6 +36,8 @@ const char* LayoutKindHelper::layout_kind_as_string(LayoutKind lk) { return "NULL_FREE_ATOMIC_FLAT"; case LayoutKind::NULLABLE_ATOMIC_FLAT: return "NULLABLE_ATOMIC_FLAT"; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + return "NULLABLE_NON_ATOMIC_FLAT"; case LayoutKind::UNKNOWN: return "UNKNOWN"; default: diff --git a/src/hotspot/share/oops/layoutKind.hpp b/src/hotspot/share/oops/layoutKind.hpp index fb108a94632..36358b07060 100644 --- a/src/hotspot/share/oops/layoutKind.hpp +++ b/src/hotspot/share/oops/layoutKind.hpp @@ -66,6 +66,17 @@ // null marker). The reset value instance is needed because the VM needs an instance guaranteed to // always be filled with zeros, and the default value could have its null marker set to non-zero if // it is used as a source to update a NULLABLE_ATOMIC_FLAT field. +// NULLABLE_NON_ATOMIC_FLAT: this is a special layout, only used for strict final non-static fields. Because strict +// final non-static fields cannot be updated after the call to the super constructor, there's no +// concurrency issue on those fields, so they can be flattened even if they are nullable. During the +// construction of the instance, the uninitializedThis reference cannot escape before the call to +// the super's constructor, so no concurrent reads are possible when the field is initialized. After +// the call to the super's constructor, no update is possible because the field is strict and final, +// so no write possible during a read. This field has a null marker similar to the one of the +// NULLABLE_ATOMIC_FLAT layout. However, there's no requirement to read the null marker and the +// rest of the value atomically. If the null marker indicates a non-null value, the fields of the +// field's value can be read independently. Same rules for a putfield, no atomicity requirement, +// as long as all fields and the null marker are up to date at the end of the putfield. // BUFFERED: this layout is only used in heap buffered instances of a value class. It is computed to be compatible // to be compatible in size and alignment with all other flat layouts supported by the value class. // @@ -79,20 +90,22 @@ enum class LayoutKind : uint32_t { NULL_FREE_NON_ATOMIC_FLAT = 2, // flat, null-free (no null marker), no guarantee of atomic updates NULL_FREE_ATOMIC_FLAT = 3, // flat, null-free, size compatible with atomic updates, alignment requirement is equal to the size NULLABLE_ATOMIC_FLAT = 4, // flat, include a null marker, plus same size/alignment properties as ATOMIC layout - UNKNOWN = 5 // used for uninitialized fields of type LayoutKind + NULLABLE_NON_ATOMIC_FLAT = 5, // flat, include a null marker, non-atomic, only used for strict final non-static fields + UNKNOWN = 6 // used for uninitialized fields of type LayoutKind }; class LayoutKindHelper : AllStatic { public: static bool is_flat(LayoutKind lk) { return lk == LayoutKind::NULL_FREE_NON_ATOMIC_FLAT - || lk == LayoutKind::NULL_FREE_ATOMIC_FLAT || lk == LayoutKind::NULLABLE_ATOMIC_FLAT; + || lk == LayoutKind::NULL_FREE_ATOMIC_FLAT + || lk == LayoutKind::NULLABLE_ATOMIC_FLAT || lk == LayoutKind::NULLABLE_NON_ATOMIC_FLAT; } static bool is_atomic_flat(LayoutKind lk) { return lk == LayoutKind::NULL_FREE_ATOMIC_FLAT || lk == LayoutKind::NULLABLE_ATOMIC_FLAT; } static bool is_nullable_flat(LayoutKind lk) { - return lk == LayoutKind::NULLABLE_ATOMIC_FLAT; + return lk == LayoutKind::NULLABLE_ATOMIC_FLAT || lk == LayoutKind::NULLABLE_NON_ATOMIC_FLAT; } static const char* layout_kind_as_string(LayoutKind lk); }; diff --git a/src/hotspot/share/oops/markWord.cpp b/src/hotspot/share/oops/markWord.cpp index 36fdcfc5c95..46cff5a7df2 100644 --- a/src/hotspot/share/oops/markWord.cpp +++ b/src/hotspot/share/oops/markWord.cpp @@ -112,6 +112,8 @@ markWord markWord::flat_array_prototype(LayoutKind lk) { case LayoutKind::NULLABLE_ATOMIC_FLAT: return markWord(nullable_flat_array_pattern); break; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + ShouldNotReachHere(); default: ShouldNotReachHere(); } diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 9aea5f8a608..add49d61437 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -837,6 +837,9 @@ const int ObjectAlignmentInBytes = 8; product(bool, UseAtomicValueFlattening, true, \ "Allow the JVM to flatten some atomic values") \ \ + product(bool, UseNullableNonAtomicValueFlattening, false, \ + "Allow the JVM to flatten some strict final non-static fields") \ + \ product(intx, FlatArrayElementMaxOops, 4, \ "Max nof embedded object references in an inline type to flatten, <0 no limit") \ \ diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/EmptyValueTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/EmptyValueTest.java new file mode 100644 index 00000000000..89ad2630ffd --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/EmptyValueTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java EmptyValueTest.java + * @run main/othervm -XX:+UseNullableNonAtomicValueFlattening EmptyValueTest + */ + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import runtime.valhalla.inlinetypes.field_layout.FieldLayoutAnalyzer; + +public class EmptyValueTest { + + static class TestRunner { + public static void main(String[] args) throws Exception { + Class testClass = Class.forName("EmptyValueTest"); + Asserts.assertNotNull(testClass); + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("test_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + System.out.println("Running " + test.getName()); + test.invoke(null); + } + } + } + } + + static value class Empty0 {} + + static class Container0 { + Empty0 empty; + } + + static public void test_0() { + Container0 c = new Container0(); + Asserts.assertNull(c.empty); + c.empty = new Empty0(); + Asserts.assertNotNull(c.empty); + c.empty = null; + Asserts.assertNull(c.empty); + } + + static public void check_0(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("EmptyValueTest$Container0"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("empty", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_NON_ATOMIC_FLAT, f.layoutKind()); + } + + static value class WrappedEmpty1 { + @Strict + @NullRestricted + Empty0 empty = new Empty0(); + } + + static class Container1 { + WrappedEmpty1 we = new WrappedEmpty1(); + } + + static public void test_1() { + Container1 c = new Container1(); + } + + static public void check_1(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("EmptyValueTest$Container1"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("we", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_ATOMIC_FLAT, f.layoutKind()); + } + + static class Container2 { + @Strict + @NullRestricted + WrappedEmpty1 we = new WrappedEmpty1(); + } + + static public void test_2() { + Container2 c = new Container2(); + } + + static public void check_2(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("EmptyValueTest$Container2"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("we", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULL_FREE_NON_ATOMIC_FLAT, f.layoutKind()); + } + + static ProcessBuilder exec(String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-Xint"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, "-XX:+UseNullableNonAtomicValueFlattening"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") + "."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + public static void main(String[] args) throws Exception { + + // Generate test classes + EmptyValueTest sft = new EmptyValueTest(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec("EmptyValueTest$TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + System.out.print(out.getOutput()); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // To help during test development + System.out.print(out.getOutput()); + + // Get and parse the test output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + + // Running tests verification method (check that tests produced the right configuration) + Class testClass = EmptyValueTest.class; + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("check_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + test.invoke(null, fla); + } + } + + // Verify that all layouts are correct + fla.check(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java index d04d64d634a..1f0f747a997 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java @@ -38,7 +38,7 @@ public class FieldLayoutAnalyzer { // Mutable wrapper around log output to manage the cursor while parsing - static class LogOutput { + public static class LogOutput { List lines; int cursor; @@ -78,11 +78,12 @@ static BlockType parseType(String s) { } } - static enum LayoutKind { + public static enum LayoutKind { NON_FLAT, NULL_FREE_NON_ATOMIC_FLAT, NULL_FREE_ATOMIC_FLAT, - NULLABLE_ATOMIC_FLAT; + NULLABLE_ATOMIC_FLAT, + NULLABLE_NON_ATOMIC_FLAT; static LayoutKind parseLayoutKind(String s) { switch(s) { @@ -90,6 +91,7 @@ static LayoutKind parseLayoutKind(String s) { case "NULL_FREE_NON_ATOMIC_FLAT" : return NULL_FREE_NON_ATOMIC_FLAT; case "NULL_FREE_ATOMIC_FLAT" : return NULL_FREE_ATOMIC_FLAT; case "NULLABLE_ATOMIC_FLAT" : return NULLABLE_ATOMIC_FLAT; + case "NULLABLE_NON_ATOMIC_FLAT" : return NULLABLE_NON_ATOMIC_FLAT; default: throw new RuntimeException("Unknown layout kind: " + s); } @@ -127,10 +129,6 @@ void print(PrintStream out) { static FieldBlock parseField(String line) { String[] fieldLine = line.split("\\s+"); - // for(String s : fieldLine) { - // System.out.print("["+s+"]"); // debugging statement to be removed - // } - // System.out.println(); int offset = Integer.parseInt(fieldLine[1].substring(1, fieldLine[1].length())); BlockType type = BlockType.parseType(fieldLine[2]); String[] size_align = fieldLine[3].split("/"); @@ -175,7 +173,7 @@ static FieldBlock parseField(String line) { } - static class ClassLayout { + public static class ClassLayout { String name; String superName; boolean isValue; @@ -187,8 +185,10 @@ static class ClassLayout { int nonAtomicLayoutAlignment; // -1 if no non-nullable layout int atomicLayoutSize; // -1 if no atomic layout int atomicLayoutAlignment; // -1 if no atomic layout - int nullableLayoutSize; // -1 if no nullable layout - int nullableLayoutAlignment; // -1 if no nullable layout + int nullableAtomicLayoutSize; // -1 if no nullable layout + int nullableAtomicLayoutAlignment; // -1 if no nullable layout + int nullableNonAtomicLayoutSize; + int nullableNonAtomicLayoutAlignment; int nullMarkerOffset; // -1 if no nullable layout String[] lines; ArrayList staticFields; @@ -201,7 +201,8 @@ private ClassLayout() { boolean hasNonAtomicLayout() { return nonAtomicLayoutSize != -1; } boolean hasAtomicLayout() { return atomicLayoutSize != -1; } - boolean hasNullableLayout() { return nullableLayoutSize != -1; } + boolean hasNullableAtomicLayout() { return nullableAtomicLayoutSize != -1; } + boolean hasNullableNonAtomicLayout() { return nullableNonAtomicLayoutSize != -1; } boolean hasNullMarker() {return nullMarkerOffset != -1; } int getSize(LayoutKind layoutKind) { @@ -215,8 +216,11 @@ int getSize(LayoutKind layoutKind) { Asserts.assertTrue(atomicLayoutSize != -1); return atomicLayoutSize; case NULLABLE_ATOMIC_FLAT: - Asserts.assertTrue(nullableLayoutSize != -1); - return nullableLayoutSize; + Asserts.assertTrue(nullableAtomicLayoutSize != -1); + return nullableAtomicLayoutSize; + case NULLABLE_NON_ATOMIC_FLAT: + Asserts.assertTrue(nullableNonAtomicLayoutSize != -1); + return nullableNonAtomicLayoutSize; default: throw new RuntimeException("Unknown LayoutKind " + layoutKind); } @@ -233,8 +237,11 @@ int getAlignment(LayoutKind layoutKind) { Asserts.assertTrue(atomicLayoutSize != -1); return atomicLayoutAlignment; case NULLABLE_ATOMIC_FLAT: - Asserts.assertTrue(nullableLayoutSize != -1); - return nullableLayoutAlignment; + Asserts.assertTrue(nullableAtomicLayoutSize != -1); + return nullableAtomicLayoutAlignment; + case NULLABLE_NON_ATOMIC_FLAT: + Asserts.assertTrue(nullableNonAtomicLayoutSize != -1); + return nullableNonAtomicLayoutAlignment; default: throw new RuntimeException("Unknown LayoutKind " + layoutKind); } @@ -322,21 +329,34 @@ static ClassLayout parseClassLayout(LogOutput lo) { cl.atomicLayoutAlignment = Integer.parseInt(size_align[1]); } lo.moveToNextLine(); - // NULLABLE_ATOMIC_FLAT layout: x/y + // Nullable atomic flat layout: x/y Asserts.assertTrue(lo.getCurrentLine().startsWith("NULLABLE_ATOMIC_FLAT layout")); - String[] nullableLayoutLine = lo.getCurrentLine().split("\\s+"); - size_align = nullableLayoutLine[2].split("/"); + String[] nullableAtomicLayoutLine = lo.getCurrentLine().split("\\s+"); + size_align = nullableAtomicLayoutLine[2].split("/"); if (size_align[0].contentEquals("-")) { Asserts.assertTrue(size_align[1].contentEquals("-"), "Size/Alignment mismatch"); - cl.nullableLayoutSize = -1; - cl.nullableLayoutAlignment = -1; + cl.nullableAtomicLayoutSize = -1; + cl.nullableAtomicLayoutAlignment = -1; } else { - cl.nullableLayoutSize = Integer.parseInt(size_align[0]); - cl.nullableLayoutAlignment = Integer.parseInt(size_align[1]); + cl.nullableAtomicLayoutSize = Integer.parseInt(size_align[0]); + cl.nullableAtomicLayoutAlignment = Integer.parseInt(size_align[1]); + } + lo.moveToNextLine(); + // Nullable non-atomic flat layout: x/y + Asserts.assertTrue(lo.getCurrentLine().startsWith("NULLABLE_NON_ATOMIC_FLAT layout")); + String[] nullableNonAtomicLayoutLine = lo.getCurrentLine().split("\\s+"); + size_align = nullableNonAtomicLayoutLine[2].split("/"); + if (size_align[0].contentEquals("-")) { + Asserts.assertTrue(size_align[1].contentEquals("-"), "Size/Alignment mismatch"); + cl.nullableNonAtomicLayoutSize = -1; + cl.nullableNonAtomicLayoutAlignment = -1; + } else { + cl.nullableNonAtomicLayoutSize = Integer.parseInt(size_align[0]); + cl.nullableNonAtomicLayoutAlignment = Integer.parseInt(size_align[1]); } lo.moveToNextLine(); // Null marker offset = 15 (if class has a nullable flat layout) - if (cl.nullableLayoutSize != -1) { + if (cl.nullableAtomicLayoutSize != -1 || cl.nullableNonAtomicLayoutSize != -1) { Asserts.assertTrue(lo.getCurrentLine().startsWith("Null marker offset")); String[] nullMarkerLine = lo.getCurrentLine().split("\\s+"); cl.nullMarkerOffset = Integer.parseInt(nullMarkerLine[4]); @@ -347,7 +367,6 @@ static ClassLayout parseClassLayout(LogOutput lo) { } else { cl.isValue = false; } - Asserts.assertTrue(lo.getCurrentLine().startsWith("---"), lo.getCurrentLine()); lo.moveToNextLine(); return cl; @@ -361,7 +380,7 @@ FieldBlock getFieldAtOffset(int offset, boolean isStatic) { throw new RuntimeException("No " + (isStatic ? "static" : "nonstatic") + " field found at offset "+ offset); } - FieldBlock getFieldFromName(String fieldName, boolean isStatic) { + public FieldBlock getFieldFromName(String fieldName, boolean isStatic) { FieldBlock block = getFieldFromNameOrNull(fieldName, isStatic); if (block == null) { throw new RuntimeException("No " + (isStatic ? "static" : "nonstatic") + " field found with name "+ fieldName); @@ -408,7 +427,7 @@ ClassLayout getClassLayout(String name) { return null; } - ClassLayout getClassLayoutFromName(String name) { + public ClassLayout getClassLayoutFromName(String name) { System.out.println("We have the layouts"); for(ClassLayout layout : layouts) { System.out.println("- " + layout.name); @@ -652,7 +671,7 @@ void checkNullMarkers() { last_type = block.type; if (block.type() == BlockType.NULL_MARKER) { Asserts.assertTrue(layout.hasNullMarker()); - Asserts.assertTrue(layout.hasNullableLayout()); + Asserts.assertTrue(layout.hasNullableAtomicLayout() || layout.hasNullableNonAtomicLayout()); Asserts.assertEQ(block.offset(), layout.nullMarkerOffset); } if (block.type() == BlockType.EMPTY) has_empty_slot = true; @@ -672,7 +691,7 @@ void checkNullMarkers() { } } - void check() { + public void check() { checkOffsets(); checkNoOverlap(); checkSizeAndAlignment(); diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/StrictFinalTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/StrictFinalTest.java new file mode 100644 index 00000000000..ed0752cff80 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/StrictFinalTest.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java StrictFinalTest.java + * @run main/othervm -XX:+UseNullableNonAtomicValueFlattening StrictFinalTest + */ + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import runtime.valhalla.inlinetypes.field_layout.FieldLayoutAnalyzer; + +public class StrictFinalTest { + + static class TestRunner { + public static void main(String[] args) throws Exception { + Class testClass = Class.forName("StrictFinalTest"); + Asserts.assertNotNull(testClass); + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("test_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + System.out.println("Running " + test.getName()); + test.invoke(null); + } + } + } + } + + @LooselyConsistentValue + static value class Value0 { + // Just big enough to be bigger than 64 bits with the null marker + int i = 0; + int j = 0; + } + + static value class Container0 { + Value0 val0 = new Value0(); + } + + static public void test_0() { + Container0 c = new Container0(); + } + + static public void check_0(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container0"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_NON_ATOMIC_FLAT, f.layoutKind()); + } + + static value class Value1 { + // Just big enough to be bigger than 64 bits with the null marker + int i = 0; + int j = 0; + } + + static value class Container1 { + Value1 val0 = new Value1(); + } + + static public void test_1() { + Container1 c = new Container1(); + } + + static public void check_1(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container1"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // Value classes' fields are always strict and final, must be flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_NON_ATOMIC_FLAT, f.layoutKind()); + } + + static class Container2 { + Value1 val0 = new Value1(); + } + + + static public void test_2() { + Container2 c = new Container2(); + } + + static public void check_2(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container2"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // Not strict nor final, must not be flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f.layoutKind()); + } + + // Test temporarily disabled, to be be re-enabled when strict non-final fields are supported + // + // static class Container3 { + // @Strict + // Value1 val0 = new Value1(); + // } + + + // static public void test_3() { + // Container3 c = new Container3(); + // } + + // static public void check_3(FieldLayoutAnalyzer fla) { + // FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container3"); + // FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // // Not final, must not be flattened + // Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f.layoutKind()); + // } + + static class Container4 { + final Value1 val0 = new Value1(); + } + + + static public void test_4() { + Container4 c = new Container4(); + } + + static public void check_4(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container4"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // Not strict, must not be flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f.layoutKind()); + } + + static class Container5 { + @Strict + final Value1 val0 = new Value1(); + } + + static public void test_5() { + Container5 c = new Container5(); + Asserts.assertNotNull(c.val0); + } + + static public void check_5(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container5"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // Strict and final, must be flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_NON_ATOMIC_FLAT, f.layoutKind()); + } + + static class Container6 { + @Strict + final Value1 val0 = null; + } + + static public void test_6() { + Container6 c = new Container6(); + Asserts.assertNull(c.val0); + } + + static public void check_6(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container6"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // Strict and final, must be flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_NON_ATOMIC_FLAT, f.layoutKind()); + } + + @LooselyConsistentValue + static value class Container7 { + Value1 val0 = new Value1(); + } + + static public void test_7() { + Container7 c = new Container7(); + Asserts.assertNotNull(c.val0); + } + + static public void check_7(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("StrictFinalTest$Container7"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + // Container is not atomic, must not flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f.layoutKind()); + } + + static ProcessBuilder exec(String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-Xint"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, "-XX:+UseNullableNonAtomicValueFlattening"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") + "."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + public static void main(String[] args) throws Exception { + + // Generate test classes + StrictFinalTest sft = new StrictFinalTest(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec("StrictFinalTest$TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + System.out.print(out.getOutput()); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // To help during test development + System.out.print(out.getOutput()); + + // Get and parse the test output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + + // Running tests verification method (check that tests produced the right configuration) + Class testClass = StrictFinalTest.class; + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("check_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + test.invoke(null, fla); + } + } + + // Verify that all layouts are correct + fla.check(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/TEST.properties b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/TEST.properties new file mode 100644 index 00000000000..0775c6c4ccf --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/TEST.properties @@ -0,0 +1 @@ +maxOutputSize = 100000000