diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 4a487c97814..5ce03ae322e 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -555,7 +555,7 @@ JRT_ENTRY(int, Runtime1::substitutability_check(JavaThread* current, oopDesc* le JavaValue result(T_BOOLEAN); JavaCalls::call_static(&result, vmClasses::ValueObjectMethods_klass(), - vmSymbols::isSubstitutable_name(), + UseAltSubstitutabilityMethod ? vmSymbols::isSubstitutableAlt_name() : vmSymbols::isSubstitutable_name(), vmSymbols::object_object_boolean_signature(), &args, CHECK_0); return result.get_jboolean() ? 1 : 0; diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 056b2aaf9c1..0ce957bda05 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -1383,6 +1383,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, assert(nullptr == _fields_type_annotations, "invariant"); bool is_inline_type = !class_access_flags.is_identity_class() && !class_access_flags.is_abstract(); + bool is_value_class = !class_access_flags.is_identity_class() && !class_access_flags.is_interface(); cfs->guarantee_more(2, CHECK); // length const u2 length = cfs->get_u2_fast(); *java_fields_count_ptr = length; @@ -1394,7 +1395,8 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, // two more slots are required for inline classes: // one for the static field with a reference to the pre-allocated default value // one for the field the JVM injects when detecting an empty inline class - const int total_fields = length + num_injected + (is_inline_type ? 2 : 0); + const int total_fields = length + num_injected + (is_inline_type ? 2 : 0) + + ((UseAltSubstitutabilityMethod && is_value_class) ? 1 : 0); // Allocate a temporary resource array to collect field data. // After parsing all fields, data are stored in a UNSIGNED5 compressed stream. @@ -1575,6 +1577,22 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, _temp_field_info->adr_at(idx2)->set_index(idx2); _static_oop_count++; } + if (!access_flags().is_identity_class() && !access_flags().is_interface() + && _class_name != vmSymbols::java_lang_Object() && UseAltSubstitutabilityMethod) { + // Acmp map required for abstract and concrete value classes + FieldInfo::FieldFlags fflags2(0); + fflags2.update_injected(true); + fflags2.update_stable(true); + AccessFlags aflags2(JVM_ACC_STATIC | JVM_ACC_FINAL); + FieldInfo fi3(aflags2, + (u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(acmp_maps_name)), + (u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(int_array_signature)), + 0, + fflags2); + int idx2 = _temp_field_info->append(fi3); + _temp_field_info->adr_at(idx2)->set_index(idx2); + _static_oop_count++; + } if (_need_verify && length > 1) { // Check duplicated fields @@ -5524,6 +5542,40 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, vk->initialize_calling_convention(CHECK); } + if (EnableValhalla && !access_flags().is_identity_class() && !access_flags().is_interface() + && _class_name != vmSymbols::java_lang_Object() && UseAltSubstitutabilityMethod) { + // Both abstract and concrete value classes need a field map for acmp + ik->set_acmp_maps_offset(_layout_info->_acmp_maps_offset); + // Current format of acmp maps: + // All maps are stored contiguously in a single int array because it might + // be too early to instantiate an Object array (to be investigated) + // Format is: + // [number_of_nonoop_entries][offset0][size[0][offset1][size1]...[oop_offset0][oop_offset1]... + // ^ ^ + // | | + // --------------------- Pair of integer describing a segment of + // contiguous non-oop fields + // First element is the number of segment of contiguous non-oop fields + // Then, each segment of contiguous non-oop fields is described by two consecutive elements: + // the offset then the size. + // After the last segment of contiguous non-oop fields, oop fields are described, one element + // per oop field, containing the offset of the field. + int nonoop_acmp_map_size = _layout_info->_nonoop_acmp_map->length() * 2; + int oop_acmp_map_size = _layout_info->_oop_acmp_map->length(); + typeArrayOop map = oopFactory::new_intArray(nonoop_acmp_map_size + oop_acmp_map_size + 1, CHECK); + typeArrayHandle map_h(THREAD, map); + map_h->int_at_put(0, _layout_info->_nonoop_acmp_map->length()); + for (int i = 0; i < _layout_info->_nonoop_acmp_map->length(); i++) { + map_h->int_at_put(i * 2 + 1, _layout_info->_nonoop_acmp_map->at(i).first); + map_h->int_at_put(i * 2 + 2, _layout_info->_nonoop_acmp_map->at(i).second); + } + int oop_map_start = nonoop_acmp_map_size + 1; + for (int i = 0; i < _layout_info->_oop_acmp_map->length(); i++) { + map_h->int_at_put(oop_map_start + i, _layout_info->_oop_acmp_map->at(i)); + } + ik->java_mirror()->obj_field_put(ik->acmp_maps_offset(), map_h()); + } + ClassLoadingService::notify_class_loaded(ik, false /* not shared class */); if (!is_internal()) { diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp index ecf872ccb21..23c646fc0fa 100644 --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -32,6 +32,7 @@ #include "oops/instanceKlass.hpp" #include "oops/typeArrayOop.hpp" #include "utilities/accessFlags.hpp" +#include "utilities/pair.hpp" class Annotations; template @@ -71,6 +72,8 @@ class OopMapBlocksBuilder : public ResourceObj { class FieldLayoutInfo : public ResourceObj { public: OopMapBlocksBuilder* oop_map_blocks; + GrowableArray>* _nonoop_acmp_map; + GrowableArray* _oop_acmp_map; int _instance_size; int _nonstatic_field_size; int _static_field_size; @@ -83,11 +86,20 @@ class FieldLayoutInfo : public ResourceObj { int _nullable_layout_size_in_bytes; int _null_marker_offset; int _null_reset_value_offset; + int _acmp_maps_offset; bool _has_nonstatic_fields; bool _is_naturally_atomic; bool _must_be_atomic; bool _has_inline_fields; bool _is_empty_inline_klass; + FieldLayoutInfo() : oop_map_blocks(nullptr), _nonoop_acmp_map(nullptr), _oop_acmp_map(nullptr), + _instance_size(-1), _nonstatic_field_size(-1), _static_field_size(-1), + _payload_alignment(-1), _payload_offset(-1), _payload_size_in_bytes(-1), + _non_atomic_size_in_bytes(-1), _non_atomic_alignment(-1), + _atomic_layout_size_in_bytes(-1), _nullable_layout_size_in_bytes(-1), + _null_marker_offset(-1), _null_reset_value_offset(-1), _acmp_maps_offset(-1), + _has_nonstatic_fields(false), _is_naturally_atomic(false), _must_be_atomic(false), + _has_inline_fields(false), _is_empty_inline_klass(false) { } }; // Parser for for .class files diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index e5805c59467..fa29ddd1849 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -211,6 +211,7 @@ FieldLayout::FieldLayout(GrowableArray* field_info, Arrayadr_at(block->field_index())->name(_cp) == vmSymbols::null_reset_value_name()) { _null_reset_value_offset = block->offset(); } + if (_field_info->adr_at(block->field_index())->name(_cp) == vmSymbols::acmp_maps_name()) { + _acmp_maps_offset = block->offset(); + } } if (block->block_kind() == LayoutRawBlock::FLAT && block->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) { int nm_offset = block->inline_klass()->null_marker_offset() - block->inline_klass()->payload_offset() + block->offset(); @@ -1249,6 +1253,9 @@ void FieldLayoutBuilder::compute_inline_class_layout() { _static_layout->add(_static_fields->big_primitive_fields()); _static_layout->add(_static_fields->small_primitive_fields()); + if (UseAltSubstitutabilityMethod) { + generate_acmp_maps(); + } epilogue(); } @@ -1288,6 +1295,156 @@ void FieldLayoutBuilder::register_embedded_oops(OopMapBlocksBuilder* nonstatic_o register_embedded_oops_from_list(nonstatic_oop_maps, group->small_primitive_fields()); } +static int insert_segment(GrowableArray>* map, int offset, int size, int last_idx) { + if (map->is_empty()) { + return map->append(Pair(offset, size)); + } + last_idx = last_idx == -1 ? 0 : last_idx; + int start = map->adr_at(last_idx)->first > offset ? 0 : last_idx; + bool inserted = false; + for (int c = start; c < map->length(); c++) { + if (offset == (map->adr_at(c)->first + map->adr_at(c)->second)) { + //contiguous to the last field, can be coalesced + map->adr_at(c)->second = map->adr_at(c)->second + size; + inserted = true; + break; // break out of the for loop + } + if (offset < (map->adr_at(c)->first)) { + map->insert_before(c, Pair(offset, size)); + last_idx = c; + inserted = true; + break; // break out of the for loop + } + } + if (!inserted) { + last_idx = map->append(Pair(offset, size)); + } + return last_idx; +} + +static int insert_map_at_offset(GrowableArray>* nonoop_map, GrowableArray* oop_map, + const InstanceKlass* ik, int offset, int payload_offset, int last_idx) { + oop mirror = ik->java_mirror(); + oop array = mirror->obj_field(ik->acmp_maps_offset()); + assert(array != nullptr, "Sanity check"); + typeArrayOop fmap = (typeArrayOop)array; + typeArrayHandle fmap_h(Thread::current(), fmap); + int nb_nonoop_field = fmap_h->int_at(0); + int field_offset = offset - payload_offset; + for (int i = 0; i < nb_nonoop_field; i++) { + last_idx = insert_segment(nonoop_map, + field_offset + fmap_h->int_at( i * 2 + 1), + fmap_h->int_at( i * 2 + 2), last_idx); + } + int len = fmap_h->length(); + for (int i = nb_nonoop_field * 2 + 1; i < len; i++) { + oop_map->append(field_offset + fmap_h->int_at(i)); + } + return last_idx; +} + +static void split_after(GrowableArray>* map, int idx, int head) { + int offset = map->adr_at(idx)->first; + int size = map->adr_at(idx)->second; + if (size <= head) return; + map->adr_at(idx)->first = offset + head; + map->adr_at(idx)->second = size - head; + map->insert_before(idx, Pair(offset, head)); + +} + +void FieldLayoutBuilder::generate_acmp_maps() { + assert(_is_inline_type || _is_abstract_value, "Must be done only for value classes (abstract or not)"); + + // create/initialize current class' maps + // The Pair values in the nonoop_acmp_map represent segments of memory + _nonoop_acmp_map = new GrowableArray>(); + _oop_acmp_map = new GrowableArray(); + if (_is_empty_inline_class) return; + // last_idx remembers the position of the last insertion in order to speed up the next insertion. + // Local fields are processed in ascending offset order, so an insertion is very likely be performed + // next to the previous insertion. However, in some cases local fields and inherited fields can be + // interleaved, in which case the search of the insertion position cannot depend on the previous insertion. + int last_idx = -1; + if (_super_klass != nullptr && _super_klass != vmClasses::Object_klass()) { // Assumes j.l.Object cannot have fields + last_idx = insert_map_at_offset(_nonoop_acmp_map, _oop_acmp_map, _super_klass, 0, 0, last_idx); + } + + // Processing local fields + LayoutRawBlock* b = _layout->blocks(); + while(b != _layout->last_block()) { + switch(b->block_kind()) { + case LayoutRawBlock::RESERVED: + case LayoutRawBlock::EMPTY: + case LayoutRawBlock::PADDING: + case LayoutRawBlock::NULL_MARKER: + case LayoutRawBlock::INHERITED: // inherited fields are handled during maps creation/initialization + // skip + break; + + case LayoutRawBlock::REGULAR: + { + FieldInfo* fi = _field_info->adr_at(b->field_index()); + if (fi->signature(_constant_pool)->starts_with("L") || fi->signature(_constant_pool)->starts_with("[")) { + _oop_acmp_map->append(b->offset()); + } else { + // Non-oop case + last_idx = insert_segment(_nonoop_acmp_map, b->offset(), b->size(), last_idx); + } + break; + } + case LayoutRawBlock::FLAT: + { + InlineKlass* vk = b->inline_klass(); + last_idx = insert_map_at_offset(_nonoop_acmp_map, _oop_acmp_map, vk, b->offset(), vk->payload_offset(), last_idx); + if (b->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) { + int null_marker_offset = b->offset() + vk->null_marker_offset_in_payload(); + last_idx = insert_segment(_nonoop_acmp_map, null_marker_offset, 1, last_idx); + // Important note: the implementation assumes that for nullable flat fields, if the + // null marker is zero (field is null), then all the fields of the flat field are also + // zeroed. So, nullable flat field are not encoded different than null-free flat fields, + // all fields are included in the map, plus the null marker + // If it happens that the assumption above is wrong, then nullable flat fields would + // require a dedicated section in the acmp map, and be handled differently: null_marker + // comparison first, and if null markers are identical and non-zero, then conditional + // comparison of the other fields + } + } + break; + + } + b = b->next_block(); + } + + // split segments into well-aligned blocks + int idx = 0; + while (idx < _nonoop_acmp_map->length()) { + int offset = _nonoop_acmp_map->adr_at(idx)->first; + int size = _nonoop_acmp_map->adr_at(idx)->second; + int mod = offset % 8; + switch (mod) { + case 0: + break; + case 4: + split_after(_nonoop_acmp_map, idx, 4); + break; + case 2: + case 6: + split_after(_nonoop_acmp_map, idx, 2); + break; + case 1: + case 3: + case 5: + case 7: + split_after(_nonoop_acmp_map, idx, 1); + break; + default: + ShouldNotReachHere(); + } + idx++; + } +} + void FieldLayoutBuilder::epilogue() { // Computing oopmaps OopMapBlocksBuilder* nonstatic_oop_maps = @@ -1338,6 +1495,13 @@ void FieldLayoutBuilder::epilogue() { _info->_is_empty_inline_klass = _is_empty_inline_class; } + // Acmp maps are needed for both concrete and abstract value classes + if (UseAltSubstitutabilityMethod && (_is_inline_type || _is_abstract_value)) { + _info->_acmp_maps_offset = _static_layout->acmp_maps_offset(); + _info->_nonoop_acmp_map = _nonoop_acmp_map; + _info->_oop_acmp_map = _oop_acmp_map; + } + // This may be too restrictive, since if all the fields fit in 64 // bits we could make the decision to align instances of this class // to 64-bit boundaries, and load and store them as single words. @@ -1411,6 +1575,18 @@ void FieldLayoutBuilder::epilogue() { if (_null_marker_offset != -1) { st.print_cr("Null marker offset = %d", _null_marker_offset); } + if (UseAltSubstitutabilityMethod) { + st.print("Non-oop acmp map: "); + for (int i = 0 ; i < _nonoop_acmp_map->length(); i++) { + st.print("<%d,%d>, ", _nonoop_acmp_map->at(i).first, _nonoop_acmp_map->at(i).second); + } + st.print_cr(""); + st.print("oop acmp map: "); + for (int i = 0 ; i < _oop_acmp_map->length(); i++) { + st.print("%d, ", _oop_acmp_map->at(i)); + } + st.print_cr(""); + } } st.print_cr("---"); // Print output all together. diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp index 0c857f84412..f47cab8f777 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp @@ -196,6 +196,7 @@ class FieldLayout : public ResourceObj { int _super_alignment; int _super_min_align_required; int _null_reset_value_offset; // offset of the reset value in class mirror, only for static layout of inline classes + int _acmp_maps_offset; bool _super_has_fields; bool _has_inherited_fields; @@ -224,6 +225,10 @@ class FieldLayout : public ResourceObj { assert(_null_reset_value_offset != -1, "Must have been set"); return _null_reset_value_offset; } + int acmp_maps_offset() const { + assert(_acmp_maps_offset != -1, "Must have been set"); + return _acmp_maps_offset; + } bool super_has_fields() const { return _super_has_fields; } bool has_inherited_fields() const { return _has_inherited_fields; } @@ -281,6 +286,8 @@ class FieldLayoutBuilder : public ResourceObj { FieldGroup* _static_fields; FieldLayout* _layout; FieldLayout* _static_layout; + GrowableArray>* _nonoop_acmp_map; + GrowableArray* _oop_acmp_map; int _nonstatic_oopmap_count; int _payload_alignment; int _payload_offset; @@ -337,6 +344,7 @@ class FieldLayoutBuilder : public ResourceObj { void add_flat_field_oopmap(OopMapBlocksBuilder* nonstatic_oop_map, InlineKlass* vk, int offset); void register_embedded_oops_from_list(OopMapBlocksBuilder* nonstatic_oop_maps, GrowableArray* list); void register_embedded_oops(OopMapBlocksBuilder* nonstatic_oop_maps, FieldGroup* group); + void generate_acmp_maps(); }; #endif // SHARE_CLASSFILE_FIELDLAYOUTBUILDER_HPP diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 87d83072a91..1e1f09ec128 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -529,6 +529,7 @@ class SerializeClosure; template(resolved_references_name, "") \ template(init_lock_name, "") \ template(null_reset_value_name, ".null_reset") \ + template(acmp_maps_name, ".acmp_maps") \ template(empty_marker_name, ".empty") \ template(address_size_name, "ADDRESS_SIZE0") \ template(page_size_name, "PAGE_SIZE") \ @@ -775,6 +776,7 @@ class SerializeClosure; \ template(java_lang_runtime_ValueObjectMethods, "java/lang/runtime/ValueObjectMethods") \ template(isSubstitutable_name, "isSubstitutable") \ + template(isSubstitutableAlt_name, "isSubstitutableAlt") \ template(valueObjectHashCode_name, "valueObjectHashCode") \ template(jdk_internal_value_PrimitiveClass, "jdk/internal/value/PrimitiveClass") \ template(jdk_internal_value_ValueClass, "jdk/internal/value/ValueClass") \ diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index f6e21fb5e2f..c7429c3b77f 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -331,7 +331,7 @@ JRT_ENTRY(jboolean, InterpreterRuntime::is_substitutable(JavaThread* current, oo JavaCallArguments args; args.push_oop(ha); args.push_oop(hb); - methodHandle method(current, Universe::is_substitutable_method()); + methodHandle method(current, UseAltSubstitutabilityMethod ? Universe::is_substitutableAlt_method() : Universe::is_substitutable_method()); method->method_holder()->initialize(CHECK_false); // Ensure class ValueObjectMethods is initialized JavaCalls::call(&result, method, &args, THREAD); if (HAS_PENDING_EXCEPTION) { diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 2d2a910fced..f692af0a26b 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -117,6 +117,7 @@ static LatestMethodCache _throw_no_such_method_error_cache; // Unsafe.throwNoSuc static LatestMethodCache _do_stack_walk_cache; // AbstractStackWalker.doStackWalk() static LatestMethodCache _is_substitutable_cache; // ValueObjectMethods.isSubstitutable() static LatestMethodCache _value_object_hash_code_cache; // ValueObjectMethods.valueObjectHashCode() +static LatestMethodCache _is_substitutable_alt_cache; // ValueObjectMethods.isSubstitutableAlt() // Known objects TypeArrayKlass* Universe::_typeArrayKlasses[T_LONG+1] = { nullptr /*, nullptr...*/ }; @@ -1072,6 +1073,7 @@ Method* Universe::throw_no_such_method_error() { return _throw_no_such_method Method* Universe::do_stack_walk_method() { return _do_stack_walk_cache.get_method(); } Method* Universe::is_substitutable_method() { return _is_substitutable_cache.get_method(); } Method* Universe::value_object_hash_code_method() { return _value_object_hash_code_cache.get_method(); } +Method* Universe::is_substitutableAlt_method() { return _is_substitutable_alt_cache.get_method(); } void Universe::initialize_known_methods(JavaThread* current) { // Set up static method for registering finalizers @@ -1112,6 +1114,10 @@ void Universe::initialize_known_methods(JavaThread* current) { vmClasses::ValueObjectMethods_klass(), vmSymbols::valueObjectHashCode_name()->as_C_string(), vmSymbols::object_int_signature(), true); + _is_substitutable_alt_cache.init(current, + vmClasses::ValueObjectMethods_klass(), + vmSymbols::isSubstitutableAlt_name()->as_C_string(), + vmSymbols::object_object_boolean_signature(), true); } void universe2_init() { diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index e317ed02d04..019ff6c49d6 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -258,6 +258,7 @@ class Universe: AllStatic { static Method* is_substitutable_method(); static Method* value_object_hash_code_method(); + static Method* is_substitutableAlt_method(); static oop the_null_sentinel(); static address the_null_sentinel_addr() { return (address) &_the_null_sentinel; } diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index ec29367d42f..30049c144bc 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -593,6 +593,7 @@ InstanceKlass::InstanceKlass(const ClassFileParser& parser, KlassKind kind, mark _nest_host_index(0), _init_state(allocated), _reference_type(reference_type), + _acmp_maps_offset(0), _init_thread(nullptr), _inline_layout_info_array(nullptr), _loadable_descriptors(nullptr), diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 9cd74b412a2..ef759f0bdad 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -292,6 +292,9 @@ class InstanceKlass: public Klass { volatile ClassState _init_state; // state of class u1 _reference_type; // reference type + int _acmp_maps_offset; // offset to injected static field storing acmp_maps for values classes + // unfortunately, abstract values need one too so it cannot be stored in + // the InlineKlassFixedBlock that only exist for InlineKlass. // State is set either at parse time or while executing, atomically to not disturb other state InstanceKlassFlags _misc_flags; @@ -644,6 +647,12 @@ class InstanceKlass: public Klass { // reference type ReferenceType reference_type() const { return (ReferenceType)_reference_type; } + int acmp_maps_offset() const { + assert(_acmp_maps_offset != 0, "Not initialized"); + return _acmp_maps_offset; + } + void set_acmp_maps_offset(int offset) { _acmp_maps_offset = offset; } + // this class cp index u2 this_class_index() const { return _this_class_index; } void set_this_class_index(u2 index) { _this_class_index = index; } diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index b164421fb3a..35c6232433f 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -2299,7 +2299,8 @@ void Parse::do_acmp(BoolTest::mask btest, Node* left, Node* right) { set_all_memory(mem); kill_dead_locals(); - ciMethod* subst_method = ciEnv::current()->ValueObjectMethods_klass()->find_method(ciSymbols::isSubstitutable_name(), ciSymbols::object_object_boolean_signature()); + ciSymbol* subst_method_name = UseAltSubstitutabilityMethod ? ciSymbols::isSubstitutableAlt_name() : ciSymbols::isSubstitutable_name(); + ciMethod* subst_method = ciEnv::current()->ValueObjectMethods_klass()->find_method(subst_method_name, ciSymbols::object_object_boolean_signature()); CallStaticJavaNode *call = new CallStaticJavaNode(C, TypeFunc::make(subst_method), SharedRuntime::get_resolve_static_call_stub(), subst_method); call->set_override_symbolic_info(true); call->init_req(TypeFunc::Parms, not_null_left); diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index e06ddb97961..c135a222e43 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -962,6 +962,19 @@ UNSAFE_ENTRY(jint, Unsafe_ArrayInstanceIndexScale0(JNIEnv *env, jobject unsafe, } } UNSAFE_END +UNSAFE_ENTRY(jarray, Unsafe_GetFieldMap0(JNIEnv* env, jobject unsafe, jclass clazz)) { + oop mirror = JNIHandles::resolve_non_null(clazz); + Klass* k = java_lang_Class::as_Klass(mirror); + + if (!k->is_inline_klass()) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Argument is not a concrete value class"); + } + InlineKlass* vk = InlineKlass::cast(k); + oop map = mirror->obj_field(vk->acmp_maps_offset()); + return (jarray) JNIHandles::make_local(THREAD, map); +} UNSAFE_END + + UNSAFE_ENTRY(jlong, Unsafe_GetObjectSize0(JNIEnv* env, jobject o, jobject obj)) oop p = JNIHandles::resolve(obj); return p->size() * HeapWordSize; @@ -1239,6 +1252,7 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "arrayInstanceBaseOffset0", CC "(" OBJ_ARR ")I", FN_PTR(Unsafe_ArrayInstanceBaseOffset0)}, {CC "arrayIndexScale0", CC "(" CLS ")I", FN_PTR(Unsafe_ArrayIndexScale0)}, {CC "arrayInstanceIndexScale0", CC "(" OBJ_ARR ")I", FN_PTR(Unsafe_ArrayInstanceIndexScale0)}, + {CC "getFieldMap0", CC "(Ljava/lang/Class;)[I", FN_PTR(Unsafe_GetFieldMap0)}, {CC "getObjectSize0", CC "(Ljava/lang/Object;)J", FN_PTR(Unsafe_GetObjectSize0)}, {CC "defineClass0", CC "(" DC_Args ")" CLS, FN_PTR(Unsafe_DefineClass0)}, diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index bd66bc71948..a2c2ac474e1 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -2002,6 +2002,10 @@ jint Arguments::parse_vm_init_args(GrowableArrayCHeapinitialize(CHECK_NH); LinkResolver::resolve_invoke(callinfo, receiver, attached_method, bc, false, CHECK_NH); #ifdef ASSERT - Method* is_subst = vmClasses::ValueObjectMethods_klass()->find_method(vmSymbols::isSubstitutable_name(), vmSymbols::object_object_boolean_signature()); + Symbol* subst_method_name = UseAltSubstitutabilityMethod ? vmSymbols::isSubstitutableAlt_name() : vmSymbols::isSubstitutable_name(); + Method* is_subst = vmClasses::ValueObjectMethods_klass()->find_method(subst_method_name, vmSymbols::object_object_boolean_signature()); assert(callinfo.selected_method() == is_subst, "must be isSubstitutable method"); #endif return receiver; diff --git a/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java index 5f7247fb48e..d076ad81b6d 100644 --- a/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java +++ b/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java @@ -49,8 +49,10 @@ import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.Unsafe; import jdk.internal.value.LayoutIteration; import jdk.internal.value.ValueClass; + import sun.invoke.util.Wrapper; import static java.lang.invoke.MethodHandles.constant; @@ -72,6 +74,7 @@ * private entry points called by VM. */ final class ValueObjectMethods { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private ValueObjectMethods() {} private static final boolean VERBOSE = System.getProperty("value.bsm.debug") != null; @@ -1402,4 +1405,47 @@ private static void recursiveChecks(MethodHandle base, int baseCount) { throw new IllegalArgumentException("missing leading MethodHandle parameters: " + mt); } + private static boolean isSubstitutableAlt(Object a, Object b) { + // This method assumes a and b are not null and their are both instances of the same value class + final Unsafe U = UNSAFE; + int[] map = U.getFieldMap(a.getClass()); + int nbNonRef = map[0]; + for (int i = 0; i < nbNonRef; i++) { + int offset = map[i * 2 + 1]; + int size = map[i * 2 + 2]; + int nlong = size / 8; + for (int j = 0; j < nlong; j++) { + long la = U.getLong(a, offset); + long lb = U.getLong(b, offset); + if (la != lb) return false; + } + size -= nlong * 8; + int nint = size / 4; + for (int j = 0; j < nint; j++) { + int ia = U.getInt(a, offset); + int ib = U.getInt(b, offset); + if (ia != ib) return false; + } + size -= nint * 4; + int nshort = size / 2; + for (int j = 0; j < nshort; j++) { + short sa = U.getShort(a, offset); + short sb = U.getShort(b, offset); + if (sa != sb) return false; + } + size -= nshort * 2; + for (int j = 0; j < size; j++) { + byte ba = U.getByte(a, offset); + byte bb = U.getByte(b, offset); + if (ba != bb) return false; + } + } + for (int i = nbNonRef * 2 + 1; i < map.length; i++) { + int offset = map[i]; + Object oa = U.getReference(a, offset); + Object ob = U.getReference(b, offset); + if (oa != ob) return false; + } + return true; + } } diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index 31db2daa9cc..a90ad689577 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -1517,6 +1517,13 @@ public int arrayInstanceIndexScale(Object[] array) { return arrayInstanceIndexScale0(array); } + public int[] getFieldMap(Class c) { + if (c == null) { + throw new NullPointerException(); + } + return getFieldMap0(c); + } + /** * Return the size of the object in the heap. * @param o an object @@ -4449,6 +4456,7 @@ private void putShortParts(Object o, long offset, byte i0, byte i1) { private native int arrayInstanceIndexScale0(Object[] array); private native long getObjectSize0(Object o); private native int getLoadAverage0(double[] loadavg, int nelems); + private native int[] getFieldMap0(Class c); /** diff --git a/test/jdk/valhalla/valuetypes/RecursiveValueClass.java b/test/jdk/valhalla/valuetypes/RecursiveValueClass.java index e12084776f9..d15b2801932 100644 --- a/test/jdk/valhalla/valuetypes/RecursiveValueClass.java +++ b/test/jdk/valhalla/valuetypes/RecursiveValueClass.java @@ -280,6 +280,8 @@ private static N build() { public void largeGraph() { N node = build(); long start = System.nanoTime(); + // With the alternate isSubstitutable() method, type recursion is handled differently + // and the line below won't throw a SOE assertThrows(StackOverflowError.class, () -> { boolean v = node.l == node.r; }); assertThrows(StackOverflowError.class, () -> { int hc = node.hashCode(); }); System.out.format("testing large graph: %d ms%n", (System.nanoTime() - start) / 1000);