From e18277b470a162b9668297e8e286c812c4b0b604 Mon Sep 17 00:00:00 2001 From: Radim Vansa Date: Thu, 12 Jun 2025 12:29:15 +0000 Subject: [PATCH] 8352075: Perf regression accessing fields Reviewed-by: coleenp, iklam, jsjolen --- .../share/classfile/classFileParser.cpp | 8 + .../share/classfile/classFileParser.hpp | 3 +- .../share/classfile/fieldLayoutBuilder.cpp | 4 +- src/hotspot/share/classfile/javaClasses.cpp | 7 + src/hotspot/share/oops/fieldInfo.cpp | 231 ++++++++++++++++-- src/hotspot/share/oops/fieldInfo.hpp | 35 ++- src/hotspot/share/oops/fieldInfo.inline.hpp | 34 ++- src/hotspot/share/oops/fieldStreams.hpp | 29 ++- .../share/oops/fieldStreams.inline.hpp | 37 ++- src/hotspot/share/oops/instanceKlass.cpp | 28 ++- src/hotspot/share/oops/instanceKlass.hpp | 4 + .../share/prims/jvmtiRedefineClasses.cpp | 7 + src/hotspot/share/runtime/globals.hpp | 4 + src/hotspot/share/utilities/packedTable.cpp | 113 +++++++++ src/hotspot/share/utilities/packedTable.hpp | 123 ++++++++++ src/hotspot/share/utilities/unsigned5.hpp | 11 +- .../gtest/utilities/test_packedTable.cpp | 158 ++++++++++++ .../FieldStream/LocalFieldLookupTest.java | 140 +++++++++++ 18 files changed, 905 insertions(+), 71 deletions(-) create mode 100644 src/hotspot/share/utilities/packedTable.cpp create mode 100644 src/hotspot/share/utilities/packedTable.hpp create mode 100644 test/hotspot/gtest/utilities/test_packedTable.cpp create mode 100644 test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index bfb41f8384a..edec019bd70 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -3740,6 +3740,7 @@ void ClassFileParser::apply_parsed_class_metadata( _cp->set_pool_holder(this_klass); this_klass->set_constants(_cp); this_klass->set_fieldinfo_stream(_fieldinfo_stream); + this_klass->set_fieldinfo_search_table(_fieldinfo_search_table); this_klass->set_fields_status(_fields_status); this_klass->set_methods(_methods); this_klass->set_inner_classes(_inner_classes); @@ -3749,6 +3750,8 @@ void ClassFileParser::apply_parsed_class_metadata( this_klass->set_permitted_subclasses(_permitted_subclasses); this_klass->set_record_components(_record_components); + DEBUG_ONLY(FieldInfoStream::validate_search_table(_cp, _fieldinfo_stream, _fieldinfo_search_table)); + // Delay the setting of _local_interfaces and _transitive_interfaces until after // initialize_supers() in fill_instance_klass(). It is because the _local_interfaces could // be shared with _transitive_interfaces and _transitive_interfaces may be shared with @@ -5056,6 +5059,7 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, // note that is not safe to use the fields in the parser from this point on assert(nullptr == _cp, "invariant"); assert(nullptr == _fieldinfo_stream, "invariant"); + assert(nullptr == _fieldinfo_search_table, "invariant"); assert(nullptr == _fields_status, "invariant"); assert(nullptr == _methods, "invariant"); assert(nullptr == _inner_classes, "invariant"); @@ -5276,6 +5280,7 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream, _super_klass(), _cp(nullptr), _fieldinfo_stream(nullptr), + _fieldinfo_search_table(nullptr), _fields_status(nullptr), _methods(nullptr), _inner_classes(nullptr), @@ -5352,6 +5357,7 @@ void ClassFileParser::clear_class_metadata() { // deallocated if classfile parsing returns an error. _cp = nullptr; _fieldinfo_stream = nullptr; + _fieldinfo_search_table = nullptr; _fields_status = nullptr; _methods = nullptr; _inner_classes = nullptr; @@ -5374,6 +5380,7 @@ ClassFileParser::~ClassFileParser() { if (_fieldinfo_stream != nullptr) { MetadataFactory::free_array(_loader_data, _fieldinfo_stream); } + MetadataFactory::free_array(_loader_data, _fieldinfo_search_table); if (_fields_status != nullptr) { MetadataFactory::free_array(_loader_data, _fields_status); @@ -5774,6 +5781,7 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st _fieldinfo_stream = FieldInfoStream::create_FieldInfoStream(_temp_field_info, _java_fields_count, injected_fields_count, loader_data(), CHECK); + _fieldinfo_search_table = FieldInfoStream::create_search_table(_cp, _fieldinfo_stream, _loader_data, CHECK); _fields_status = MetadataFactory::new_array(_loader_data, _temp_field_info->length(), FieldStatus(0), CHECK); diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp index 8f9f4ebea4d..707fbf6985f 100644 --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -123,6 +123,7 @@ class ClassFileParser { const InstanceKlass* _super_klass; ConstantPool* _cp; Array* _fieldinfo_stream; + Array* _fieldinfo_search_table; Array* _fields_status; Array* _methods; Array* _inner_classes; diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index b6007923907..03afe89f4f8 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -301,7 +301,7 @@ void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance BasicType last_type; int last_offset = -1; while (ik != nullptr) { - for (AllFieldStream fs(ik->fieldinfo_stream(), ik->constants()); !fs.done(); fs.next()) { + for (AllFieldStream fs(ik); !fs.done(); fs.next()) { BasicType type = Signature::basic_type(fs.signature()); // distinction between static and non-static fields is missing if (fs.access_flags().is_static()) continue; @@ -461,7 +461,7 @@ void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlas bool found = false; const InstanceKlass* ik = super; while (!found && ik != nullptr) { - for (AllFieldStream fs(ik->fieldinfo_stream(), ik->constants()); !fs.done(); fs.next()) { + for (AllFieldStream fs(ik); !fs.done(); fs.next()) { if (fs.offset() == b->offset()) { output->print_cr(" @%d \"%s\" %s %d/%d %s", b->offset(), diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 8d32d73fb47..0e8467289ad 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -967,6 +967,13 @@ void java_lang_Class::fixup_mirror(Klass* k, TRAPS) { Array* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, k->class_loader_data(), CHECK); ik->set_fieldinfo_stream(new_fis); MetadataFactory::free_array(k->class_loader_data(), old_stream); + + Array* old_table = ik->fieldinfo_search_table(); + Array* search_table = FieldInfoStream::create_search_table(ik->constants(), new_fis, k->class_loader_data(), CHECK); + ik->set_fieldinfo_search_table(search_table); + MetadataFactory::free_array(k->class_loader_data(), old_table); + + DEBUG_ONLY(FieldInfoStream::validate_search_table(ik->constants(), new_fis, search_table)); } } diff --git a/src/hotspot/share/oops/fieldInfo.cpp b/src/hotspot/share/oops/fieldInfo.cpp index 300b45277ad..d0825ba6df8 100644 --- a/src/hotspot/share/oops/fieldInfo.cpp +++ b/src/hotspot/share/oops/fieldInfo.cpp @@ -22,8 +22,11 @@ * */ +#include "memory/resourceArea.hpp" +#include "cds/cdsConfig.hpp" #include "oops/fieldInfo.inline.hpp" #include "runtime/atomic.hpp" +#include "utilities/packedTable.hpp" void FieldInfo::print(outputStream* os, ConstantPool* cp) { os->print_cr("index=%d name_index=%d name=%s signature_index=%d signature=%s offset=%d " @@ -37,8 +40,10 @@ void FieldInfo::print(outputStream* os, ConstantPool* cp) { field_flags().as_uint(), initializer_index(), generic_signature_index(), - _field_flags.is_injected() ? lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8(), - contended_group()); + _field_flags.is_generic() ? (_field_flags.is_injected() ? + lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8() + ) : "", + is_contended() ? contended_group() : 0); } void FieldInfo::print_from_growable_array(outputStream* os, GrowableArray* array, ConstantPool* cp) { @@ -62,13 +67,17 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie StreamSizer s; StreamFieldSizer sizer(&s); + assert(fields->length() == java_fields + injected_fields, "must be"); + sizer.consumer()->accept_uint(java_fields); sizer.consumer()->accept_uint(injected_fields); for (int i = 0; i < fields->length(); i++) { FieldInfo* fi = fields->adr_at(i); sizer.map_field_info(*fi); } - int storage_size = sizer.consumer()->position() + 1; + // Originally there was an extra byte with 0 terminating the reading; + // now we check limits instead. + int storage_size = sizer.consumer()->position(); Array* const fis = MetadataFactory::new_array(loader_data, storage_size, CHECK_NULL); using StreamWriter = UNSIGNED5::Writer*, int, ArrayHelper*, int>>; @@ -79,15 +88,14 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie writer.consumer()->accept_uint(java_fields); writer.consumer()->accept_uint(injected_fields); for (int i = 0; i < fields->length(); i++) { - FieldInfo* fi = fields->adr_at(i); - writer.map_field_info(*fi); + writer.map_field_info(fields->at(i)); } #ifdef ASSERT FieldInfoReader r(fis); - int jfc = r.next_uint(); + int jfc, ifc; + r.read_field_counts(&jfc, &ifc); assert(jfc == java_fields, "Must be"); - int ifc = r.next_uint(); assert(ifc == injected_fields, "Must be"); for (int i = 0; i < jfc + ifc; i++) { FieldInfo fi; @@ -113,30 +121,221 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie return fis; } -GrowableArray* FieldInfoStream::create_FieldInfoArray(const Array* fis, int* java_fields_count, int* injected_fields_count) { - int length = FieldInfoStream::num_total_fields(fis); - GrowableArray* array = new GrowableArray(length); +int FieldInfoStream::compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2) { + int cmp = n1->fast_compare(n2); + return cmp != 0 ? cmp : s1->fast_compare(s2); +} + + +// We use both name and signature during the comparison; while JLS require unique +// names for fields, JVMS requires only unique name + signature combination. +struct field_pos { + Symbol* _name; + Symbol* _signature; + int _index; + int _position; +}; + +class FieldInfoSupplier: public PackedTableBuilder::Supplier { + const field_pos* _positions; + size_t _elements; + +public: + FieldInfoSupplier(const field_pos* positions, size_t elements): _positions(positions), _elements(elements) {} + + bool next(uint32_t* key, uint32_t* value) override { + if (_elements == 0) { + return false; + } + *key = _positions->_position; + *value = _positions->_index; + ++_positions; + --_elements; + return true; + } +}; + +Array* FieldInfoStream::create_search_table(ConstantPool* cp, const Array* fis, ClassLoaderData* loader_data, TRAPS) { + if (CDSConfig::is_dumping_dynamic_archive()) { + // We cannot use search table; in case of dynamic archives it should be sorted by "requested" addresses, + // but Symbol* addresses are coming from _constants, which has "buffered" addresses. + // For background, see new comments inside allocate_node_impl in symbolTable.cpp + return nullptr; + } + FieldInfoReader r(fis); - *java_fields_count = r.next_uint(); - *injected_fields_count = r.next_uint(); + int java_fields; + int injected_fields; + r.read_field_counts(&java_fields, &injected_fields); + assert(java_fields >= 0, "must be"); + if (java_fields == 0 || fis->length() == 0 || static_cast(java_fields) < BinarySearchThreshold) { + return nullptr; + } + + ResourceMark rm; + field_pos* positions = NEW_RESOURCE_ARRAY(field_pos, java_fields); + for (int i = 0; i < java_fields; ++i) { + assert(r.has_next(), "number of fields must match"); + + positions[i]._position = r.position(); + FieldInfo fi; + r.read_field_info(fi); + + positions[i]._name = fi.name(cp); + positions[i]._signature = fi.signature(cp); + positions[i]._index = i; + } + auto compare_pair = [](const void* v1, const void* v2) { + const field_pos* p1 = reinterpret_cast(v1); + const field_pos* p2 = reinterpret_cast(v2); + return compare_name_and_sig(p1->_name, p1->_signature, p2->_name, p2->_signature); + }; + qsort(positions, java_fields, sizeof(field_pos), compare_pair); + + PackedTableBuilder builder(fis->length() - 1, java_fields - 1); + Array* table = MetadataFactory::new_array(loader_data, java_fields * builder.element_bytes(), CHECK_NULL); + FieldInfoSupplier supplier(positions, java_fields); + builder.fill(table->data(), static_cast(table->length()), supplier); + return table; +} + +GrowableArray* FieldInfoStream::create_FieldInfoArray(const Array* fis, int* java_fields_count, int* injected_fields_count) { + FieldInfoReader r(fis); + r.read_field_counts(java_fields_count, injected_fields_count); + int length = *java_fields_count + *injected_fields_count; + + GrowableArray* array = new GrowableArray(length); while (r.has_next()) { FieldInfo fi; r.read_field_info(fi); array->append(fi); } assert(array->length() == length, "Must be"); - assert(array->length() == *java_fields_count + *injected_fields_count, "Must be"); return array; } void FieldInfoStream::print_from_fieldinfo_stream(Array* fis, outputStream* os, ConstantPool* cp) { - int length = FieldInfoStream::num_total_fields(fis); FieldInfoReader r(fis); - int java_field_count = r.next_uint(); - int injected_fields_count = r.next_uint(); + int java_fields_count; + int injected_fields_count; + r.read_field_counts(&java_fields_count, &injected_fields_count); while (r.has_next()) { FieldInfo fi; r.read_field_info(fi); fi.print(os, cp); } } + +class FieldInfoComparator: public PackedTableLookup::Comparator { + const FieldInfoReader* _reader; + ConstantPool* _cp; + const Symbol* _name; + const Symbol* _signature; + +public: + FieldInfoComparator(const FieldInfoReader* reader, ConstantPool* cp, const Symbol* name, const Symbol* signature): + _reader(reader), _cp(cp), _name(name), _signature(signature) {} + + int compare_to(uint32_t position) override { + FieldInfoReader r2(*_reader); + r2.set_position_and_next_index(position, -1); + u2 name_index, sig_index; + r2.read_name_and_signature(&name_index, &sig_index); + Symbol* mid_name = _cp->symbol_at(name_index); + Symbol* mid_sig = _cp->symbol_at(sig_index); + + return FieldInfoStream::compare_name_and_sig(_name, _signature, mid_name, mid_sig); + } + +#ifdef ASSERT + void reset(uint32_t position) override { + FieldInfoReader r2(*_reader); + r2.set_position_and_next_index(position, -1); + u2 name_index, signature_index; + r2.read_name_and_signature(&name_index, &signature_index); + _name = _cp->symbol_at(name_index); + _signature = _cp->symbol_at(signature_index); + } +#endif // ASSERT +}; + +#ifdef ASSERT +void FieldInfoStream::validate_search_table(ConstantPool* cp, const Array* fis, const Array* search_table) { + if (search_table == nullptr) { + return; + } + FieldInfoReader reader(fis); + int java_fields, injected_fields; + reader.read_field_counts(&java_fields, &injected_fields); + assert(java_fields > 0, "must be"); + + PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table); + assert(lookup.element_bytes() * java_fields == static_cast(search_table->length()), "size does not match"); + + FieldInfoComparator comparator(&reader, cp, nullptr, nullptr); + // Check 1: assert that elements have the correct order based on the comparison function + lookup.validate_order(comparator); + + // Check 2: Iterate through the original stream (not just search_table) and try if lookup works as expected + reader.set_position_and_next_index(0, 0); + reader.read_field_counts(&java_fields, &injected_fields); + while (reader.has_next()) { + int field_start = reader.position(); + FieldInfo fi; + reader.read_field_info(fi); + if (fi.field_flags().is_injected()) { + // checking only java fields that precede injected ones + break; + } + + FieldInfoReader r2(fis); + int index = r2.search_table_lookup(search_table, fi.name(cp), fi.signature(cp), cp, java_fields); + assert(index == static_cast(fi.index()), "wrong index: %d != %u", index, fi.index()); + assert(index == r2.next_index(), "index should match"); + assert(field_start == r2.position(), "must find the same position"); + } +} +#endif // ASSERT + +void FieldInfoStream::print_search_table(outputStream* st, ConstantPool* cp, const Array* fis, const Array* search_table) { + if (search_table == nullptr) { + return; + } + FieldInfoReader reader(fis); + int java_fields, injected_fields; + reader.read_field_counts(&java_fields, &injected_fields); + assert(java_fields > 0, "must be"); + PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table); + auto printer = [&] (size_t offset, uint32_t position, uint32_t index) { + reader.set_position_and_next_index(position, -1); + u2 name_index, sig_index; + reader.read_name_and_signature(&name_index, &sig_index); + Symbol* name = cp->symbol_at(name_index); + Symbol* sig = cp->symbol_at(sig_index); + st->print(" [%zu] #%d,#%d = ", offset, name_index, sig_index); + name->print_symbol_on(st); + st->print(":"); + sig->print_symbol_on(st); + st->print(" @ %p,%p", name, sig); + st->cr(); + }; + + lookup.iterate(printer); +} + +int FieldInfoReader::search_table_lookup(const Array* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields) { + assert(java_fields >= 0, "must be"); + if (java_fields == 0) { + return -1; + } + FieldInfoComparator comp(this, cp, name, signature); + PackedTableLookup lookup(_r.limit() - 1, java_fields - 1, search_table); + uint32_t position; + static_assert(sizeof(uint32_t) == sizeof(_next_index), "field size assert"); + if (lookup.search(comp, &position, reinterpret_cast(&_next_index))) { + _r.set_position(static_cast(position)); + return _next_index; + } else { + return -1; + } +} diff --git a/src/hotspot/share/oops/fieldInfo.hpp b/src/hotspot/share/oops/fieldInfo.hpp index 8740d539c8f..a98895da9cf 100644 --- a/src/hotspot/share/oops/fieldInfo.hpp +++ b/src/hotspot/share/oops/fieldInfo.hpp @@ -222,29 +222,28 @@ public: void map_field_info(const FieldInfo& fi); }; - // Gadget for decoding and reading the stream of field records. class FieldInfoReader { - friend class FieldInfoStream; - friend class ClassFileParser; - friend class FieldStreamBase; - friend class FieldInfo; - UNSIGNED5::Reader _r; int _next_index; - public: +public: FieldInfoReader(const Array* fi); - private: - uint32_t next_uint() { return _r.next_uint(); } +private: + inline uint32_t next_uint() { return _r.next_uint(); } void skip(int n) { int s = _r.try_skip(n); assert(s == n,""); } public: - int has_next() { return _r.has_next(); } - int position() { return _r.position(); } - int next_index() { return _next_index; } + void read_field_counts(int* java_fields, int* injected_fields); + int has_next() const { return _r.position() < _r.limit(); } + int position() const { return _r.position(); } + int next_index() const { return _next_index; } + void read_name_and_signature(u2* name_index, u2* signature_index); void read_field_info(FieldInfo& fi); + + int search_table_lookup(const Array* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields); + // skip a whole field record, both required and optional bits FieldInfoReader& skip_field_info(); @@ -271,6 +270,11 @@ class FieldInfoStream : AllStatic { friend class JavaFieldStream; friend class FieldStreamBase; friend class ClassFileParser; + friend class FieldInfoReader; + friend class FieldInfoComparator; + + private: + static int compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2); public: static int num_java_fields(const Array* fis); @@ -278,9 +282,14 @@ class FieldInfoStream : AllStatic { static int num_total_fields(const Array* fis); static Array* create_FieldInfoStream(GrowableArray* fields, int java_fields, int injected_fields, - ClassLoaderData* loader_data, TRAPS); + ClassLoaderData* loader_data, TRAPS); + static Array* create_search_table(ConstantPool* cp, const Array* fis, ClassLoaderData* loader_data, TRAPS); static GrowableArray* create_FieldInfoArray(const Array* fis, int* java_fields_count, int* injected_fields_count); static void print_from_fieldinfo_stream(Array* fis, outputStream* os, ConstantPool* cp); + + DEBUG_ONLY(static void validate_search_table(ConstantPool* cp, const Array* fis, const Array* search_table);) + + static void print_search_table(outputStream* st, ConstantPool* cp, const Array* fis, const Array* search_table); }; class FieldStatus { diff --git a/src/hotspot/share/oops/fieldInfo.inline.hpp b/src/hotspot/share/oops/fieldInfo.inline.hpp index d3d4d765081..842393729b2 100644 --- a/src/hotspot/share/oops/fieldInfo.inline.hpp +++ b/src/hotspot/share/oops/fieldInfo.inline.hpp @@ -56,16 +56,27 @@ inline Symbol* FieldInfo::lookup_symbol(int symbol_index) const { inline int FieldInfoStream::num_injected_java_fields(const Array* fis) { FieldInfoReader fir(fis); - fir.skip(1); - return fir.next_uint(); + int java_fields_count; + int injected_fields_count; + fir.read_field_counts(&java_fields_count, &injected_fields_count); + return injected_fields_count; } inline int FieldInfoStream::num_total_fields(const Array* fis) { FieldInfoReader fir(fis); - return fir.next_uint() + fir.next_uint(); + int java_fields_count; + int injected_fields_count; + fir.read_field_counts(&java_fields_count, &injected_fields_count); + return java_fields_count + injected_fields_count; } -inline int FieldInfoStream::num_java_fields(const Array* fis) { return FieldInfoReader(fis).next_uint(); } +inline int FieldInfoStream::num_java_fields(const Array* fis) { + FieldInfoReader fir(fis); + int java_fields_count; + int injected_fields_count; + fir.read_field_counts(&java_fields_count, &injected_fields_count); + return java_fields_count; +} template inline void Mapper::map_field_info(const FieldInfo& fi) { @@ -94,13 +105,22 @@ inline void Mapper::map_field_info(const FieldInfo& fi) { inline FieldInfoReader::FieldInfoReader(const Array* fi) - : _r(fi->data(), 0), + : _r(fi->data(), fi->length()), _next_index(0) { } +inline void FieldInfoReader::read_field_counts(int* java_fields, int* injected_fields) { + *java_fields = next_uint(); + *injected_fields = next_uint(); +} + +inline void FieldInfoReader::read_name_and_signature(u2* name_index, u2* signature_index) { + *name_index = checked_cast(next_uint()); + *signature_index = checked_cast(next_uint()); +} + inline void FieldInfoReader::read_field_info(FieldInfo& fi) { fi._index = _next_index++; - fi._name_index = checked_cast(next_uint()); - fi._signature_index = checked_cast(next_uint()); + read_name_and_signature(&fi._name_index, &fi._signature_index); fi._offset = next_uint(); fi._access_flags = AccessFlags(checked_cast(next_uint())); fi._field_flags = FieldInfo::FieldFlags(next_uint()); diff --git a/src/hotspot/share/oops/fieldStreams.hpp b/src/hotspot/share/oops/fieldStreams.hpp index a1c5d77eeb6..0ae828d73d9 100644 --- a/src/hotspot/share/oops/fieldStreams.hpp +++ b/src/hotspot/share/oops/fieldStreams.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -56,17 +56,23 @@ class FieldStreamBase : public StackObj { inline FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants, int start, int limit); - inline FieldStreamBase(Array* fieldinfo_stream, ConstantPool* constants); + inline FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants); - private: + private: void initialize() { - int java_fields_count = _reader.next_uint(); - int injected_fields_count = _reader.next_uint(); - assert( _limit <= java_fields_count + injected_fields_count, "Safety check"); + int java_fields_count; + int injected_fields_count; + _reader.read_field_counts(&java_fields_count, &injected_fields_count); + if (_limit < _index) { + _limit = java_fields_count + injected_fields_count; + } else { + assert( _limit <= java_fields_count + injected_fields_count, "Safety check"); + } if (_limit != 0) { _reader.read_field_info(_fi_buf); } } + public: inline FieldStreamBase(InstanceKlass* klass); @@ -138,8 +144,11 @@ class FieldStreamBase : public StackObj { // Iterate over only the Java fields class JavaFieldStream : public FieldStreamBase { + Array* _search_table; + public: - JavaFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), 0, k->java_fields_count()) {} + JavaFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), 0, k->java_fields_count()), + _search_table(k->fieldinfo_search_table()) {} u2 name_index() const { assert(!field()->field_flags().is_injected(), "regular only"); @@ -149,7 +158,6 @@ class JavaFieldStream : public FieldStreamBase { u2 signature_index() const { assert(!field()->field_flags().is_injected(), "regular only"); return field()->signature_index(); - return -1; } u2 generic_signature_index() const { @@ -164,6 +172,10 @@ class JavaFieldStream : public FieldStreamBase { assert(!field()->field_flags().is_injected(), "regular only"); return field()->initializer_index(); } + + // Performs either a linear search or binary search through the stream + // looking for a matching name/signature combo + bool lookup(const Symbol* name, const Symbol* signature); }; @@ -176,7 +188,6 @@ class InternalFieldStream : public FieldStreamBase { class AllFieldStream : public FieldStreamBase { public: - AllFieldStream(Array* fieldinfo, ConstantPool* constants): FieldStreamBase(fieldinfo, constants) {} AllFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants()) {} }; diff --git a/src/hotspot/share/oops/fieldStreams.inline.hpp b/src/hotspot/share/oops/fieldStreams.inline.hpp index 776bcd1671c..51faf88c678 100644 --- a/src/hotspot/share/oops/fieldStreams.inline.hpp +++ b/src/hotspot/share/oops/fieldStreams.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -33,22 +33,18 @@ FieldStreamBase::FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants, int start, int limit) : _fieldinfo_stream(fieldinfo_stream), _reader(FieldInfoReader(_fieldinfo_stream)), - _constants(constantPoolHandle(Thread::current(), constants)), _index(start) { - _index = start; - if (limit < start) { - _limit = FieldInfoStream::num_total_fields(_fieldinfo_stream); - } else { - _limit = limit; - } + _constants(constantPoolHandle(Thread::current(), constants)), + _index(start), + _limit(limit) { initialize(); } -FieldStreamBase::FieldStreamBase(Array* fieldinfo_stream, ConstantPool* constants) : +FieldStreamBase::FieldStreamBase(const Array* fieldinfo_stream, ConstantPool* constants) : _fieldinfo_stream(fieldinfo_stream), _reader(FieldInfoReader(_fieldinfo_stream)), _constants(constantPoolHandle(Thread::current(), constants)), _index(0), - _limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) { + _limit(-1) { initialize(); } @@ -57,9 +53,28 @@ FieldStreamBase::FieldStreamBase(InstanceKlass* klass) : _reader(FieldInfoReader(_fieldinfo_stream)), _constants(constantPoolHandle(Thread::current(), klass->constants())), _index(0), - _limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) { + _limit(-1) { assert(klass == field_holder(), ""); initialize(); } +inline bool JavaFieldStream::lookup(const Symbol* name, const Symbol* signature) { + if (_search_table != nullptr) { + int index = _reader.search_table_lookup(_search_table, name, signature, _constants(), _limit); + if (index >= 0) { + assert(index < _limit, "must be"); + _index = index; + _reader.read_field_info(_fi_buf); + return true; + } + } else { + for (; !done(); next()) { + if (this->name() == name && this->signature() == signature) { + return true; + } + } + } + return false; +} + #endif // SHARE_OOPS_FIELDSTREAMS_INLINE_HPP diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 32d7943e1e8..1bb6b3bdf98 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -686,6 +686,11 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { } set_fieldinfo_stream(nullptr); + if (fieldinfo_search_table() != nullptr && !fieldinfo_search_table()->is_shared()) { + MetadataFactory::free_array(loader_data, fieldinfo_search_table()); + } + set_fieldinfo_search_table(nullptr); + if (fields_status() != nullptr && !fields_status()->is_shared()) { MetadataFactory::free_array(loader_data, fields_status()); } @@ -1786,13 +1791,12 @@ FieldInfo InstanceKlass::field(int index) const { } bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const { - for (JavaFieldStream fs(this); !fs.done(); fs.next()) { - Symbol* f_name = fs.name(); - Symbol* f_sig = fs.signature(); - if (f_name == name && f_sig == sig) { - fd->reinitialize(const_cast(this), fs.to_FieldInfo()); - return true; - } + JavaFieldStream fs(this); + if (fs.lookup(name, sig)) { + assert(fs.name() == name, "name must match"); + assert(fs.signature() == sig, "signature must match"); + fd->reinitialize(const_cast(this), fs.to_FieldInfo()); + return true; } return false; } @@ -2610,6 +2614,7 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) { } it->push(&_fieldinfo_stream); + it->push(&_fieldinfo_search_table); // _fields_status might be written into by Rewriter::scan_method() -> fd.set_has_initialized_final_update() it->push(&_fields_status, MetaspaceClosure::_writable); @@ -2710,6 +2715,8 @@ void InstanceKlass::remove_unshareable_info() { DEBUG_ONLY(_shared_class_load_count = 0); remove_unshareable_flags(); + + DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table)); } void InstanceKlass::remove_unshareable_flags() { @@ -2816,6 +2823,8 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl if (DiagnoseSyncOnValueBasedClasses && has_value_based_class_annotation() && !is_value_based()) { set_is_value_based(); } + + DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table)); } // Check if a class or any of its supertypes has a version older than 50. @@ -3760,6 +3769,11 @@ void InstanceKlass::print_on(outputStream* st) const { map++; } st->cr(); + + if (fieldinfo_search_table() != nullptr) { + st->print_cr(BULLET"---- field info search table:"); + FieldInfoStream::print_search_table(st, _constants, _fieldinfo_stream, _fieldinfo_search_table); + } } void InstanceKlass::print_value_on(outputStream* st) const { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 078c5b81841..55ab0996a4a 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -276,6 +276,7 @@ class InstanceKlass: public Klass { // Fields information is stored in an UNSIGNED5 encoded stream (see fieldInfo.hpp) Array* _fieldinfo_stream; + Array* _fieldinfo_search_table; Array* _fields_status; // embedded Java vtable follows here @@ -398,6 +399,9 @@ class InstanceKlass: public Klass { Array* fieldinfo_stream() const { return _fieldinfo_stream; } void set_fieldinfo_stream(Array* fis) { _fieldinfo_stream = fis; } + Array* fieldinfo_search_table() const { return _fieldinfo_search_table; } + void set_fieldinfo_search_table(Array* table) { _fieldinfo_search_table = table; } + Array* fields_status() const {return _fields_status; } void set_fields_status(Array* array) { _fields_status = array; } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index af9d973d2f1..305af6df9be 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -3550,6 +3550,13 @@ void VM_RedefineClasses::set_new_constant_pool( Array* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, scratch_class->class_loader_data(), CHECK); scratch_class->set_fieldinfo_stream(new_fis); MetadataFactory::free_array(scratch_class->class_loader_data(), old_stream); + + Array* old_table = scratch_class->fieldinfo_search_table(); + Array* search_table = FieldInfoStream::create_search_table(scratch_class->constants(), new_fis, scratch_class->class_loader_data(), CHECK); + scratch_class->set_fieldinfo_search_table(search_table); + MetadataFactory::free_array(scratch_class->class_loader_data(), old_table); + + DEBUG_ONLY(FieldInfoStream::validate_search_table(scratch_class->constants(), new_fis, search_table)); } // Update constant pool indices in the inner classes info to use diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 290ce0d3cba..75736d0dc7d 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -2005,6 +2005,10 @@ const int ObjectAlignmentInBytes = 8; product(bool, UseThreadsLockThrottleLock, true, DIAGNOSTIC, \ "Use an extra lock during Thread start and exit to alleviate" \ "contention on Threads_lock.") \ + \ + develop(uint, BinarySearchThreshold, 16, \ + "Minimal number of elements in a sorted collection to prefer" \ + "binary search over simple linear search." ) \ // end of RUNTIME_FLAGS diff --git a/src/hotspot/share/utilities/packedTable.cpp b/src/hotspot/share/utilities/packedTable.cpp new file mode 100644 index 00000000000..134cd2cd751 --- /dev/null +++ b/src/hotspot/share/utilities/packedTable.cpp @@ -0,0 +1,113 @@ +/* + * 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. + * + */ +#include + +#include "utilities/align.hpp" +#include "utilities/count_leading_zeros.hpp" +#include "utilities/packedTable.hpp" + +// The thresholds are inclusive, and in practice the limits are rounded +// to the nearest power-of-two - 1. +// Based on the max_key and max_value we figure out the number of bits required to store +// key and value; imagine that only as bits (not aligned to byte boundary... yet). +// Then we concatenate the bits for key and value, and 'add' 1-7 padding zeroes +// (high-order bits) to align on bytes. +// In the end we have each element in the table consuming 1-8 bytes (case with 0 bits for key +// is ruled out). +PackedTableBase::PackedTableBase(uint32_t max_key, uint32_t max_value) { + unsigned int key_bits = max_key == 0 ? 0 : 32 - count_leading_zeros(max_key); + unsigned int value_bits = max_value == 0 ? 0 : 32 - count_leading_zeros(max_value); + _element_bytes = align_up(key_bits + value_bits, 8) / 8; + // shifting left by 32 is undefined behaviour, and in practice returns 1 + _key_mask = key_bits >= 32 ? -1 : (1U << key_bits) - 1; + _value_shift = key_bits; + _value_mask = value_bits >= 32 ? -1 : (1U << value_bits) - 1; + guarantee(_element_bytes > 0, "wouldn't work"); + assert(_element_bytes <= sizeof(uint64_t), "shouldn't happen"); +} + +// Note: we require the supplier to provide the elements in the final order as we can't easily sort +// within this method - qsort() accepts only pure function as comparator. +void PackedTableBuilder::fill(u1* table, size_t table_length, Supplier &supplier) const { + uint32_t key, value; + size_t offset = 0; + for (; offset <= table_length && supplier.next(&key, &value); offset += _element_bytes) { + assert((key & ~_key_mask) == 0, "key out of bounds"); + assert((value & ~_value_mask) == 0, "value out of bounds: %x vs. %x (%x)", value, _value_mask, ~_value_mask); + uint64_t element = static_cast(key) | (static_cast(value) << _value_shift); + for (unsigned int i = 0; i < _element_bytes; ++i) { + table[offset + i] = static_cast(0xFF & element); + element >>= 8; + } + } + + assert(offset == table_length, "Did not fill whole array"); + assert(!supplier.next(&key, &value), "Supplier has more elements"); +} + +uint64_t PackedTableLookup::read_element(size_t offset) const { + uint64_t element = 0; + for (unsigned int i = 0; i < _element_bytes; ++i) { + element |= static_cast(_table[offset + i]) << (8 * i); + } + assert((element & ~((uint64_t) _key_mask | ((uint64_t) _value_mask << _value_shift))) == 0, "read too much"); + return element; +} + +bool PackedTableLookup::search(Comparator& comparator, uint32_t* found_key, uint32_t* found_value) const { + unsigned int low = 0, high = checked_cast(_table_length / _element_bytes); + assert(low < high, "must be"); + while (low < high) { + unsigned int mid = low + (high - low) / 2; + assert(mid >= low && mid < high, "integer overflow?"); + uint64_t element = read_element(_element_bytes * mid); + // Ignoring high 32 bits in element on purpose + uint32_t key = static_cast(element) & _key_mask; + int cmp = comparator.compare_to(key); + if (cmp == 0) { + *found_key = key; + // Since __builtin_memcpy in read_element does not copy bits outside the element + // anything above _value_mask << _value_shift should be zero. + *found_value = checked_cast(element >> _value_shift) & _value_mask; + return true; + } else if (cmp < 0) { + high = mid; + } else { + low = mid + 1; + } + } + return false; +} + +#ifdef ASSERT +void PackedTableLookup::validate_order(Comparator &comparator) const { + auto validator = [&] (size_t offset, uint32_t key, uint32_t value) { + if (offset != 0) { + assert(comparator.compare_to(key) < 0, "not sorted"); + } + comparator.reset(key); + }; + iterate(validator); +} +#endif diff --git a/src/hotspot/share/utilities/packedTable.hpp b/src/hotspot/share/utilities/packedTable.hpp new file mode 100644 index 00000000000..ed08a2b4c21 --- /dev/null +++ b/src/hotspot/share/utilities/packedTable.hpp @@ -0,0 +1,123 @@ +/* + * 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. + * + */ + +#include "oops/array.hpp" +#include "utilities/globalDefinitions.hpp" + +// Base for space-optimized structure supporting binary search. Each element +// consists of up to 32-bit key, and up to 32-bit value; these are packed +// into a bit-record with 1-byte alignment. +// The keys are ordered according to a custom comparator. +class PackedTableBase { +protected: + unsigned int _element_bytes; + uint32_t _key_mask; + unsigned int _value_shift; + uint32_t _value_mask; + +public: + PackedTableBase(uint32_t max_key, uint32_t max_value); + + // Returns number of bytes each element will occupy. + inline unsigned int element_bytes(void) const { return _element_bytes; } +}; + +// Helper class for constructing a packed table in the provided array. +class PackedTableBuilder: public PackedTableBase { +public: + class Supplier { + public: + // Returns elements with already ordered keys. + // This function should return true when the key and value was set, + // and false when there's no more elements. + // Packed table does NOT support duplicate keys. + virtual bool next(uint32_t* key, uint32_t* value) = 0; + }; + + // The thresholds are inclusive, and in practice the limits are rounded + // to the nearest power-of-two - 1. + // See PackedTableBase constructor for details. + PackedTableBuilder(uint32_t max_key, uint32_t max_value): PackedTableBase(max_key, max_value) {} + + // Constructs a packed table in the provided array, filling it with elements + // from the supplier. Note that no comparator is requied by this method - + // the supplier must return elements with already ordered keys. + // The table_length (in bytes) should match number of elements provided + // by the supplier (when Supplier::next() returns false the whole array should + // be filled). + void fill(u1* table, size_t table_length, Supplier &supplier) const; +}; + +// Helper class for lookup in a packed table. +class PackedTableLookup: public PackedTableBase { + const u1* const _table; + const size_t _table_length; + + uint64_t read_element(size_t offset) const; + +public: + + // The comparator implementation does not have to store a key (uint32_t); + // the idea is that key can point into a different structure that hosts data + // suitable for the actual comparison. That's why PackedTableLookup::search(...) + // returns the key it found as well as the value. + class Comparator { + public: + // Returns negative/0/positive if the target referred to by this comparator + // is lower/equal/higher than the target referred to by the key. + virtual int compare_to(uint32_t key) = 0; + // Changes the target this comparator refers to. + DEBUG_ONLY(virtual void reset(uint32_t key) = 0); + }; + + // The thresholds are inclusive, and in practice the limits are rounded + // to the nearest power-of-two - 1. + // See PackedTableBase constructor for details. + PackedTableLookup(uint32_t max_key, uint32_t max_value, const u1 *table, size_t table_length): + PackedTableBase(max_key, max_value), _table(table), _table_length(table_length) {} + + PackedTableLookup(uint32_t max_key, uint32_t max_value, const Array *table): + PackedTableLookup(max_key, max_value, table->data(), static_cast(table->length())) {} + + // Performs a binary search in the packed table, looking for an element with key + // referring to a target equal according to the comparator. + // When the element is found, found_key and found_value are updated from the element + // and the function returns true. + // When the element is not found, found_key and found_value are not changed and + // the function returns false. + bool search(Comparator& comparator, uint32_t* found_key, uint32_t* found_value) const; + + // Asserts that elements in the packed table follow the order defined by the comparator. + DEBUG_ONLY(void validate_order(Comparator &comparator) const); + + template + void iterate(Function func) const { + for (size_t offset = 0; offset < _table_length; offset += _element_bytes) { + uint64_t element = read_element(offset); + uint32_t key = static_cast(element) & _key_mask; + uint32_t value = checked_cast(element >> _value_shift) & _value_mask; + func(offset, key, value); + } + } +}; diff --git a/src/hotspot/share/utilities/unsigned5.hpp b/src/hotspot/share/utilities/unsigned5.hpp index 7f74d6e8498..8e3724a0012 100644 --- a/src/hotspot/share/utilities/unsigned5.hpp +++ b/src/hotspot/share/utilities/unsigned5.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -261,7 +261,7 @@ class UNSIGNED5 : AllStatic { ARR _array; OFF _limit; OFF _position; - int next_length() { + int next_length() const { return UNSIGNED5::check_length(_array, _position, _limit, GET()); } public: @@ -270,7 +270,7 @@ class UNSIGNED5 : AllStatic { uint32_t next_uint() { return UNSIGNED5::read_uint(_array, _position, _limit, GET()); } - bool has_next() { + bool has_next() const { return next_length() != 0; } // tries to skip count logical entries; returns actual number skipped @@ -284,8 +284,9 @@ class UNSIGNED5 : AllStatic { return actual; } ARR array() { return _array; } - OFF limit() { return _limit; } - OFF position() { return _position; } + OFF limit() const { return _limit; } + OFF position() const { return _position; } + void set_limit(OFF limit) { _limit = limit; } void set_position(OFF position) { _position = position; } // For debugging, even in product builds (see debug.cpp). diff --git a/test/hotspot/gtest/utilities/test_packedTable.cpp b/test/hotspot/gtest/utilities/test_packedTable.cpp new file mode 100644 index 00000000000..2adf8a1f08d --- /dev/null +++ b/test/hotspot/gtest/utilities/test_packedTable.cpp @@ -0,0 +1,158 @@ +/* + * 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. + * + */ +#include "utilities/packedTable.hpp" +#include "unittest.hpp" + +class Supplier: public PackedTableBuilder::Supplier { + uint32_t* _keys; + uint32_t* _values; + size_t _num_keys; + +public: + Supplier(uint32_t* keys, uint32_t* values, size_t num_keys): + _keys(keys), _values(values), _num_keys(num_keys) {} + + bool next(uint32_t* key, uint32_t* value) override { + if (_num_keys == 0) { + return false; + } + *key = *_keys; + ++_keys; + if (_values != nullptr) { + *value = *_values; + ++_values; + } else { + *value = 0; + } + --_num_keys; + return true; + } +}; + +class Comparator: public PackedTableLookup::Comparator { + uint32_t _current; + +public: + int compare_to(uint32_t key) override { + return _current < key ? -1 : (_current > key ? 1 : 0); + } + + void reset(uint32_t key) DEBUG_ONLY(override) { + _current = key; + } +}; + +static void test(uint32_t max_key, uint32_t max_value, unsigned int length) { + if (length > max_key + 1) { + // can't generate more keys, as keys must be unique + return; + } + PackedTableBuilder builder(max_key, max_value); + size_t table_length = length * builder.element_bytes(); + u1* table = new u1[table_length]; + + uint32_t* keys = new uint32_t[length]; + uint32_t* values = max_value != 0 ? new uint32_t[length] : nullptr; + for (unsigned int i = 0; i < length; ++i) { + keys[i] = i; + if (values != nullptr) { + values[i] = i % max_value; + } + } + Supplier sup(keys, values, length); + builder.fill(table, table_length, sup); + + Comparator comparator; + PackedTableLookup lookup(max_key, max_value, table, table_length); +#ifdef ASSERT + lookup.validate_order(comparator); +#endif + + for (unsigned int i = 0; i < length; ++i) { + uint32_t key, value; + comparator.reset(keys[i]); + EXPECT_TRUE(lookup.search(comparator, &key, &value)); + EXPECT_EQ(key, keys[i]); + if (values != nullptr) { + EXPECT_EQ(value, values[i]); + } else { + EXPECT_EQ(value, 0U); + } + } + + delete[] keys; + delete[] values; +} + +static void test_with_bits(uint32_t max_key, uint32_t max_value) { + // Some small sizes + for (unsigned int i = 0; i <= 100; ++i) { + test(max_key, max_value, i); + } + test(max_key, max_value, 10000); +} + +TEST(PackedTableLookup, lookup) { + for (int key_bits = 1; key_bits <= 32; ++key_bits) { + for (int value_bits = 0; value_bits <= 32; ++value_bits) { + test_with_bits(static_cast((1ULL << key_bits) - 1), + static_cast((1ULL << value_bits) - 1)); + } + } +} + +TEST(PackedTableBase, element_bytes) { + { + PackedTableBuilder builder(1, 0); + EXPECT_EQ(builder.element_bytes(), 1U); + } + { + PackedTableBuilder builder(15, 15); + EXPECT_EQ(builder.element_bytes(), 1U); + } + { + PackedTableBuilder builder(15, 16); + EXPECT_EQ(builder.element_bytes(), 2U); + } + { + PackedTableBuilder builder(31, 7); + EXPECT_EQ(builder.element_bytes(), 1U); + } + { + PackedTableBuilder builder(32, 7); + EXPECT_EQ(builder.element_bytes(), 2U); + } + { + PackedTableBuilder builder(-1, 0); + EXPECT_EQ(builder.element_bytes(), 4U); + } + { + PackedTableBuilder builder(-1, 1); + EXPECT_EQ(builder.element_bytes(), 5U); + } + { + PackedTableBuilder builder(-1, -1); + EXPECT_EQ(builder.element_bytes(), 8U); + } +} diff --git a/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java b/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java new file mode 100644 index 00000000000..bebef5acd0c --- /dev/null +++ b/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java @@ -0,0 +1,140 @@ +/* + * 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. + */ + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; +import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; +import static org.objectweb.asm.Opcodes.*; + +/* + * @test id=defaults + * @bug 8352075 + * @library /test/lib + * @library /testlibrary/asm + * @run main/othervm LocalFieldLookupTest + */ +/* + * @test id=custom-threshold + * @bug 8352075 + * @library /test/lib + * @library /testlibrary/asm + * @requires vm.debug == true + * @run main/othervm LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=0 LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=1 LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=15 LocalFieldLookupTest + * @run main/othervm -XX:BinarySearchThreshold=100000 LocalFieldLookupTest + */ +public class LocalFieldLookupTest { + private static final String TEST_CLASS_NAME = "Test"; + private static final int MAX_FIELDS_IN_METHOD = 10000; + + public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + // Test small classes, covering the tested thresholds + for (int i = 0; i <= 33; ++i) { + makeClass(i).newInstance(); + } + // Test classes around 256 fields (index encoding 1/2 bytes) to check off-by-one errors + for (int i = 254; i <= 259; ++i) { + makeClass(255).newInstance(); + } + // We would like to test #fields that create have the stream about 65536 bytes long; + // this value is not exposed, though, so these are rather experimentally found values, + // hence fragile. Moreover, since the stream length is incremented by about 8 bytes + // for each field we cannot test for off-by-one errors reliably. + for (int i = 8433; i <= 8437; ++i) { + makeClass(i).newInstance(); + } + // The largest class we can create - this one has 65533 entries in the constant pool + makeClass(26205).newInstance(); + } + + public static Class makeClass(int fields) throws ClassNotFoundException { + ClassWriter writer = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); + writer.visit(49, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, TEST_CLASS_NAME,null, "java/lang/Object", null); + + for (int i = 0; i < fields; i += 2) { + writer.visitField(ACC_PUBLIC, "f" + i, "I", null, null); + // Let's use duplicate names to confirm search takes signatures into account + if (i + 1 < fields) { + writer.visitField(ACC_PUBLIC, "f" + i, "J", null, null); + } + } + // We initialize fields in multiple methods to avoid running into bytecode limit per method + MethodVisitor fi = null; + for (int i = 0; i < fields; i+= 2) { + if (fi == null) { + fi = writer.visitMethod(ACC_PRIVATE, "init" + i, "()V", null, null); + fi.visitCode(); + } + fi.visitVarInsn(Opcodes.ALOAD, 0); + fi.visitInsn(Opcodes.ICONST_2); + fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "I"); + if (i + 1 < fields) { + fi.visitVarInsn(Opcodes.ALOAD, 0); + fi.visitInsn(Opcodes.LCONST_1); + fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "J"); + } + if (i % MAX_FIELDS_IN_METHOD == MAX_FIELDS_IN_METHOD - 2) { + fi.visitInsn(Opcodes.RETURN); + fi.visitMaxs(0, 0); + fi.visitEnd(); + fi = null; + } + } + if (fi != null) { + fi.visitInsn(Opcodes.RETURN); + fi.visitMaxs(0, 0); + fi.visitEnd(); + } + { + MethodVisitor mv = writer.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + for (int i = 0; i < fields; i += MAX_FIELDS_IN_METHOD) { + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, TEST_CLASS_NAME, "init" + i, "()V", false); + } + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + writer.visitEnd(); + + byte[] bytecode = writer.toByteArray(); + ClassLoader cl = new ClassLoader() { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (!TEST_CLASS_NAME.equals(name)) { + throw new ClassNotFoundException(); + } + return defineClass(TEST_CLASS_NAME, bytecode, 0, bytecode.length); + } + }; + return cl.loadClass(TEST_CLASS_NAME); + } +}