diff --git a/make/GenerateLinkOptData.gmk b/make/GenerateLinkOptData.gmk index 576bc4190b9..5052e4c0358 100644 --- a/make/GenerateLinkOptData.gmk +++ b/make/GenerateLinkOptData.gmk @@ -62,6 +62,15 @@ ifeq ($(EXTERNAL_BUILDJDK), true) INTERIM_IMAGE_DIR := $(BUILD_JDK) endif +# These are needed for deterministic classlist: +# - The classlist can be influenced by locale. Always set it to en/US. +# - Run with -Xint, as the compiler can speculatively resolve constant pool entries. +# - ForkJoinPool parallelism can cause constant pool resolution to be non-deterministic. +CLASSLIST_FILE_VM_OPTS = \ + -Duser.language=en -Duser.country=US \ + -Xint \ + -Djava.util.concurrent.ForkJoinPool.common.parallelism=0 + # Save the stderr output of the command and print it along with stdout in case # something goes wrong. $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST_JAR) @@ -69,7 +78,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST $(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $@)) $(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $(JLI_TRACE_FILE))) $(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw \ - -Duser.language=en -Duser.country=US \ + $(CLASSLIST_FILE_VM_OPTS) \ -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \ build.tools.classlist.HelloClasslist $(LOG_DEBUG) $(GREP) -v HelloClasslist $@.raw > $@.interim @@ -79,7 +88,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST $(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw.2 \ -XX:SharedClassListFile=$@.interim -XX:SharedArchiveFile=$@.jsa \ -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true \ - -Duser.language=en -Duser.country=US \ + $(CLASSLIST_FILE_VM_OPTS) \ --module-path $(SUPPORT_OUTPUTDIR)/classlist.jar \ -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \ build.tools.classlist.HelloClasslist \ diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index a98fd04ba68..72e45a7998c 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -717,6 +717,14 @@ void ArchiveBuilder::write_pointer_in_buffer(address* ptr_location, address src_ } } +void ArchiveBuilder::mark_and_relocate_to_buffered_addr(address* ptr_location) { + assert(*ptr_location != nullptr, "sanity"); + if (!is_in_mapped_static_archive(*ptr_location)) { + *ptr_location = get_buffered_addr(*ptr_location); + } + ArchivePtrMarker::mark_pointer(ptr_location); +} + address ArchiveBuilder::get_buffered_addr(address src_addr) const { SourceObjInfo* p = _src_obj_table.get(src_addr); assert(p != nullptr, "src_addr " INTPTR_FORMAT " is used but has not been archived", @@ -755,6 +763,16 @@ void ArchiveBuilder::make_klasses_shareable() { int num_obj_array_klasses = 0; int num_type_array_klasses = 0; + for (int i = 0; i < klasses()->length(); i++) { + // Some of the code in ConstantPool::remove_unshareable_info() requires the classes + // to be in linked state, so it must be call here before the next loop, which returns + // all classes to unlinked state. + Klass* k = get_buffered_addr(klasses()->at(i)); + if (k->is_instance_klass()) { + InstanceKlass::cast(k)->constants()->remove_unshareable_info(); + } + } + for (int i = 0; i < klasses()->length(); i++) { const char* type; const char* unlinked = ""; diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index cbde5a7e02c..ad0302137f5 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -408,6 +408,11 @@ public: write_pointer_in_buffer((address*)ptr_location, (address)src_addr); } + void mark_and_relocate_to_buffered_addr(address* ptr_location); + template void mark_and_relocate_to_buffered_addr(T ptr_location) { + mark_and_relocate_to_buffered_addr((address*)ptr_location); + } + address get_buffered_addr(address src_addr) const; template T get_buffered_addr(T src_addr) const { return (T)get_buffered_addr((address)src_addr); diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index 8fd20e20267..76cfa441fa7 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -357,7 +357,7 @@ void ArchiveUtils::log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) { ResourceMark rm(THREAD); int pool_index = bootstrap_specifier->bss_index(); ClassListWriter w; - w.stream()->print("%s %s", LAMBDA_PROXY_TAG, pool->pool_holder()->name()->as_C_string()); + w.stream()->print("%s %s", ClassListParser::lambda_proxy_tag(), pool->pool_holder()->name()->as_C_string()); CDSIndyInfo cii; ClassListParser::populate_cds_indy_info(pool, pool_index, &cii, CHECK); GrowableArray* indy_items = cii.items(); diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 4455dd7db1b..9ee5f25aa89 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "cds/archiveUtils.hpp" #include "cds/classListParser.hpp" +#include "cds/classPrelinker.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/metaspaceShared.hpp" #include "cds/unregisteredClasses.hpp" @@ -52,6 +53,10 @@ #include "utilities/macros.hpp" #include "utilities/utf8.hpp" +const char* ClassListParser::CONSTANT_POOL_TAG = "@cp"; +const char* ClassListParser::LAMBDA_FORM_TAG = "@lambda-form-invoker"; +const char* ClassListParser::LAMBDA_PROXY_TAG = "@lambda-proxy"; + volatile Thread* ClassListParser::_parsing_thread = nullptr; ClassListParser* ClassListParser::_instance = nullptr; @@ -299,6 +304,9 @@ void ClassListParser::parse_at_tags(TRAPS) { } } else if (strcmp(_token, LAMBDA_FORM_TAG) == 0) { LambdaFormInvokers::append(os::strdup((const char*)(_line + offset), mtInternal)); + } else if (strcmp(_token, CONSTANT_POOL_TAG) == 0) { + _token = _line + offset; + parse_constant_pool_tag(); } else { error("Invalid @ tag at the beginning of line \"%s\" line #%zu", _token, lineno()); } @@ -395,9 +403,14 @@ void ClassListParser::print_actual_interfaces(InstanceKlass* ik) { jio_fprintf(defaultStream::error_stream(), "}\n"); } -void ClassListParser::error(const char* msg, ...) { +void ClassListParser::print_diagnostic_info(outputStream* st, const char* msg, ...) { va_list ap; va_start(ap, msg); + print_diagnostic_info(st, msg, ap); + va_end(ap); +} + +void ClassListParser::print_diagnostic_info(outputStream* st, const char* msg, va_list ap) { int error_index = pointer_delta_as_int(_token, _line); if (error_index >= _line_len) { error_index = _line_len - 1; @@ -412,25 +425,34 @@ void ClassListParser::error(const char* msg, ...) { jio_vfprintf(defaultStream::error_stream(), msg, ap); if (_line_len <= 0) { - jio_fprintf(defaultStream::error_stream(), "\n"); + st->print("\n"); } else { - jio_fprintf(defaultStream::error_stream(), ":\n"); + st->print(":\n"); for (int i=0; i<_line_len; i++) { char c = _line[i]; if (c == '\0') { - jio_fprintf(defaultStream::error_stream(), "%s", " "); + st->print("%s", " "); } else { - jio_fprintf(defaultStream::error_stream(), "%c", c); + st->print("%c", c); } } - jio_fprintf(defaultStream::error_stream(), "\n"); + st->print("\n"); for (int i=0; iprint("%s", " "); } - jio_fprintf(defaultStream::error_stream(), "^\n"); + st->print("^\n"); } - va_end(ap); +} +void ClassListParser::error(const char* msg, ...) { + va_list ap; + va_start(ap, msg); + fileStream fs(defaultStream::error_stream()); + //TODO: we should write to UL/error instead, but that requires fixing some tests cases. + //LogTarget(Error, cds) lt; + //LogStream ls(lt); + print_diagnostic_info(&fs, msg, ap); + va_end(ap); vm_exit_during_initialization("class list format error.", nullptr); } @@ -453,6 +475,16 @@ void ClassListParser::check_class_name(const char* class_name) { } } +void ClassListParser::constant_pool_resolution_warning(const char* msg, ...) { + va_list ap; + va_start(ap, msg); + LogTarget(Warning, cds, resolve) lt; + LogStream ls(lt); + print_diagnostic_info(&ls, msg, ap); + ls.print("Your classlist may be out of sync with the JDK or the application."); + va_end(ap); +} + // This function is used for loading classes for customized class loaders // during archive dumping. InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS) { @@ -727,3 +759,92 @@ InstanceKlass* ClassListParser::lookup_interface_for_current_class(Symbol* inter ShouldNotReachHere(); return nullptr; } + +InstanceKlass* ClassListParser::find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop) { + Handle class_loader(current, class_loader_oop); + Handle protection_domain; + return SystemDictionary::find_instance_klass(current, class_name_symbol, class_loader, protection_domain); +} + +InstanceKlass* ClassListParser::find_builtin_class(JavaThread* current, const char* class_name) { + TempNewSymbol class_name_symbol = SymbolTable::new_symbol(class_name); + InstanceKlass* ik; + + if ( (ik = find_builtin_class_helper(current, class_name_symbol, nullptr)) != nullptr + || (ik = find_builtin_class_helper(current, class_name_symbol, SystemDictionary::java_platform_loader())) != nullptr + || (ik = find_builtin_class_helper(current, class_name_symbol, SystemDictionary::java_system_loader())) != nullptr) { + return ik; + } else { + return nullptr; + } +} + +void ClassListParser::parse_constant_pool_tag() { + if (parse_lambda_forms_invokers_only()) { + return; + } + + JavaThread* THREAD = JavaThread::current(); + skip_whitespaces(); + char* class_name = _token; + skip_non_whitespaces(); + *_token = '\0'; + _token ++; + + InstanceKlass* ik = find_builtin_class(THREAD, class_name); + if (ik == nullptr) { + _token = class_name; + if (strstr(class_name, "/$Proxy") != nullptr || + strstr(class_name, "MethodHandle$Species_") != nullptr) { + // ignore -- TODO: we should filter these out in classListWriter.cpp + } else { + constant_pool_resolution_warning("class %s is not (yet) loaded by one of the built-in loaders", class_name); + } + return; + } + + ResourceMark rm(THREAD); + constantPoolHandle cp(THREAD, ik->constants()); + GrowableArray preresolve_list(cp->length(), cp->length(), false); + bool preresolve_class = false; + bool preresolve_fmi = false; + bool preresolve_indy = false; + + while (*_token) { + int cp_index; + skip_whitespaces(); + parse_uint(&cp_index); + if (cp_index < 1 || cp_index >= cp->length()) { + constant_pool_resolution_warning("Invalid constant pool index %d", cp_index); + return; + } else { + preresolve_list.at_put(cp_index, true); + } + constantTag cp_tag = cp->tag_at(cp_index); + switch (cp_tag.value()) { + case JVM_CONSTANT_UnresolvedClass: + preresolve_class = true; + break; + case JVM_CONSTANT_UnresolvedClassInError: + case JVM_CONSTANT_Class: + // ignore + break; + case JVM_CONSTANT_Fieldref: + preresolve_fmi = true; + break; + break; + default: + constant_pool_resolution_warning("Unsupported constant pool index %d: %s (type=%d)", + cp_index, cp_tag.internal_name(), cp_tag.value()); + return; + } + } + + if (preresolve_class) { + ClassPrelinker::preresolve_class_cp_entries(THREAD, ik, &preresolve_list); + } + if (preresolve_fmi) { + ClassPrelinker::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list); + } +} + diff --git a/src/hotspot/share/cds/classListParser.hpp b/src/hotspot/share/cds/classListParser.hpp index 50ede4f6dff..540e61335d0 100644 --- a/src/hotspot/share/cds/classListParser.hpp +++ b/src/hotspot/share/cds/classListParser.hpp @@ -31,9 +31,6 @@ #include "utilities/istream.hpp" #include "utilities/resizeableResourceHash.hpp" -#define LAMBDA_PROXY_TAG "@lambda-proxy" -#define LAMBDA_FORM_TAG "@lambda-form-invoker" - class constantPoolHandle; class Thread; @@ -68,6 +65,10 @@ public: }; class ClassListParser : public StackObj { + static const char* CONSTANT_POOL_TAG; + static const char* LAMBDA_FORM_TAG; + static const char* LAMBDA_PROXY_TAG; + public: enum ParseMode { _parse_all, @@ -117,17 +118,25 @@ private: void print_actual_interfaces(InstanceKlass *ik); bool is_matching_cp_entry(const constantPoolHandle &pool, int cp_index, TRAPS); + InstanceKlass* find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop); + InstanceKlass* find_builtin_class(JavaThread* current, const char* class_name); + void resolve_indy(JavaThread* current, Symbol* class_name_symbol); void resolve_indy_impl(Symbol* class_name_symbol, TRAPS); void clean_up_input_line(); void read_class_name_and_attributes(); void parse_class_name_and_attributes(TRAPS); Klass* load_current_class(Symbol* class_name_symbol, TRAPS); + void parse_constant_pool_tag(); size_t lineno() { return _input_stream.lineno(); } FILE* do_open(const char* file); ClassListParser(const char* file, ParseMode _parse_mode); ~ClassListParser(); + void print_diagnostic_info(outputStream* st, const char* msg, va_list ap) ATTRIBUTE_PRINTF(3, 0); + void print_diagnostic_info(outputStream* st, const char* msg, ...) ATTRIBUTE_PRINTF(3, 0); + void constant_pool_resolution_warning(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0); + void error(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0); public: static void parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) { @@ -141,13 +150,18 @@ public: assert(_instance != nullptr, "must be"); return _instance; } + static const char* lambda_proxy_tag() { + return LAMBDA_PROXY_TAG; + } + static const char* lambda_form_tag() { + return LAMBDA_FORM_TAG; + } void parse(TRAPS); void split_tokens_by_whitespace(int offset, GrowableArray* items); int split_at_tag_from_line(); void parse_at_tags(TRAPS); char* _token; - void error(const char* msg, ...); void parse_int(int* value); void parse_uint(int* value); bool try_parse_uint(int* value); diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index 2a65ee51d6e..97f0bc3476e 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2024, 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 @@ -25,12 +25,15 @@ #include "precompiled.hpp" #include "cds/cds_globals.hpp" #include "cds/classListWriter.hpp" +#include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" +#include "classfile/classLoaderDataGraph.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/systemDictionaryShared.hpp" #include "memory/resourceArea.hpp" +#include "oops/constantPool.inline.hpp" #include "oops/instanceKlass.hpp" #include "runtime/mutexLocker.hpp" @@ -189,3 +192,94 @@ void ClassListWriter::delete_classlist() { delete _classlist_file; } } + +class ClassListWriter::WriteResolveConstantsCLDClosure : public CLDClosure { +public: + void do_cld(ClassLoaderData* cld) { + for (Klass* klass = cld->klasses(); klass != nullptr; klass = klass->next_link()) { + if (klass->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(klass); + write_resolved_constants_for(ik); + } + } + } +}; + +void ClassListWriter::write_resolved_constants() { + if (!is_enabled()) { + return; + } + MutexLocker lock(ClassLoaderDataGraph_lock); + MutexLocker lock2(ClassListFile_lock, Mutex::_no_safepoint_check_flag); + + WriteResolveConstantsCLDClosure closure; + ClassLoaderDataGraph::loaded_cld_do(&closure); +} + +void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { + if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data()) || + ik->is_hidden()) { + return; + } + if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) { + return; + } + if (ik->name()->equals("jdk/internal/module/SystemModules$all")) { + // This class is regenerated during JDK build process, so the classlist + // may not match the version that's in the real jdk image. + return; + } + + if (!has_id(ik)) { // do not resolve CP for classes loaded by custom loaders. + return; + } + + ResourceMark rm; + ConstantPool* cp = ik->constants(); + GrowableArray list(cp->length(), cp->length(), false); + bool print = false; + + for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused + switch (cp->tag_at(cp_index).value()) { + case JVM_CONSTANT_Class: + { + Klass* k = cp->resolved_klass_at(cp_index); + if (k->is_instance_klass()) { + list.at_put(cp_index, true); + print = true; + } + } + break; + } + } + + if (cp->cache() != nullptr) { + Array* field_entries = cp->cache()->resolved_field_entries(); + if (field_entries != nullptr) { + for (int i = 0; i < field_entries->length(); i++) { + ResolvedFieldEntry* rfe = field_entries->adr_at(i); + if (rfe->is_resolved(Bytecodes::_getstatic) || + rfe->is_resolved(Bytecodes::_putstatic) || + rfe->is_resolved(Bytecodes::_getfield) || + rfe->is_resolved(Bytecodes::_putfield)) { + list.at_put(rfe->constant_pool_index(), true); + print = true; + } + } + } + } + + if (print) { + outputStream* stream = _classlist_file; + stream->print("@cp %s", ik->name()->as_C_string()); + for (int i = 0; i < list.length(); i++) { + if (list.at(i)) { + constantTag cp_tag = cp->tag_at(i).value(); + assert(cp_tag.value() == JVM_CONSTANT_Class || + cp_tag.value() == JVM_CONSTANT_Fieldref, "sanity"); + stream->print(" %d", i); + } + } + stream->cr(); + } +} diff --git a/src/hotspot/share/cds/classListWriter.hpp b/src/hotspot/share/cds/classListWriter.hpp index a6c749fea7c..250e7ddf94b 100644 --- a/src/hotspot/share/cds/classListWriter.hpp +++ b/src/hotspot/share/cds/classListWriter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 @@ -34,6 +34,8 @@ class ClassFileStream; class ClassListWriter { #if INCLUDE_CDS class IDTable; + class WriteResolveConstantsCLDClosure; + static fileStream* _classlist_file; static IDTable* _id_table; static int _total_ids; @@ -42,6 +44,7 @@ class ClassListWriter { static int get_id(const InstanceKlass* k); static bool has_id(const InstanceKlass* k); static void assert_locked() { assert_lock_strong(ClassListFile_lock); } + static void write_resolved_constants_for(InstanceKlass* klass); public: ClassListWriter() : _locker(Thread::current(), ClassListFile_lock, Mutex::_no_safepoint_check_flag) {} @@ -66,6 +69,7 @@ public: static void init() NOT_CDS_RETURN; static void write(const InstanceKlass* k, const ClassFileStream* cfs) NOT_CDS_RETURN; static void write_to_stream(const InstanceKlass* k, outputStream* stream, const ClassFileStream* cfs = nullptr) NOT_CDS_RETURN; + static void write_resolved_constants() NOT_CDS_RETURN; static void delete_classlist() NOT_CDS_RETURN; }; diff --git a/src/hotspot/share/cds/classPrelinker.cpp b/src/hotspot/share/cds/classPrelinker.cpp index 21946ebe9c0..223d3937f93 100644 --- a/src/hotspot/share/cds/classPrelinker.cpp +++ b/src/hotspot/share/cds/classPrelinker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -26,8 +26,12 @@ #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "cds/classPrelinker.hpp" +#include "cds/regeneratedClasses.hpp" #include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" +#include "interpreter/bytecodeStream.hpp" +#include "interpreter/interpreterRuntime.hpp" #include "memory/resourceArea.hpp" #include "oops/constantPool.inline.hpp" #include "oops/instanceKlass.hpp" @@ -73,33 +77,52 @@ void ClassPrelinker::dispose() { _processed_classes = nullptr; } -bool ClassPrelinker::can_archive_resolved_klass(ConstantPool* cp, int cp_index) { +// Returns true if we CAN PROVE that cp_index will always resolve to +// the same information at both dump time and run time. This is a +// necessary (but not sufficient) condition for pre-resolving cp_index +// during CDS archive assembly. +bool ClassPrelinker::is_resolution_deterministic(ConstantPool* cp, int cp_index) { assert(!is_in_archivebuilder_buffer(cp), "sanity"); - assert(cp->tag_at(cp_index).is_klass(), "must be resolved"); - Klass* resolved_klass = cp->resolved_klass_at(cp_index); - assert(resolved_klass != nullptr, "must be"); + if (cp->tag_at(cp_index).is_klass()) { + // We require cp_index to be already resolved. This is fine for now, are we + // currently archive only CP entries that are already resolved. + Klass* resolved_klass = cp->resolved_klass_at(cp_index); + return resolved_klass != nullptr && is_class_resolution_deterministic(cp->pool_holder(), resolved_klass); + } else if (cp->tag_at(cp_index).is_field()) { + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + if (!cp->tag_at(klass_cp_index).is_klass()) { + // Not yet resolved + return false; + } + Klass* k = cp->resolved_klass_at(klass_cp_index); + if (!is_class_resolution_deterministic(cp->pool_holder(), k)) { + return false; + } - return can_archive_resolved_klass(cp->pool_holder(), resolved_klass); + if (!k->is_instance_klass()) { + // TODO: support non instance klasses as well. + return false; + } + + // Here, We don't check if this entry can actually be resolved to a valid Field/Method. + // This method should be called by the ConstantPool to check Fields/Methods that + // have already been successfully resolved. + return true; + } else { + return false; + } } -bool ClassPrelinker::can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* resolved_klass) { +bool ClassPrelinker::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) { assert(!is_in_archivebuilder_buffer(cp_holder), "sanity"); - assert(!is_in_archivebuilder_buffer(resolved_klass), "sanity"); + assert(!is_in_archivebuilder_buffer(resolved_class), "sanity"); - if (resolved_klass->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(resolved_klass); - if (is_vm_class(ik)) { // These are safe to resolve. See is_vm_class declaration. - assert(ik->is_shared_boot_class(), "vmClasses must be loaded by boot loader"); - if (cp_holder->is_shared_boot_class()) { - // For now, do this for boot loader only. Other loaders - // must go through ConstantPool::klass_at_impl at runtime - // to put this class in their directory. + if (resolved_class->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(resolved_class); - // TODO: we can support the platform and app loaders as well, if we - // preload the vmClasses into these two loaders during VM bootstrap. - return true; - } + if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) { + return false; } if (cp_holder->is_subtype_of(ik)) { @@ -108,20 +131,34 @@ bool ClassPrelinker::can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* return true; } - // TODO -- allow objArray classes, too + if (is_vm_class(ik)) { + if (ik->class_loader() != cp_holder->class_loader()) { + // At runtime, cp_holder() may not be able to resolve to the same + // ik. For example, a different version of ik may be defined in + // cp->pool_holder()'s loader using MethodHandles.Lookup.defineClass(). + return false; + } else { + return true; + } + } + } else if (resolved_class->is_objArray_klass()) { + Klass* elem = ObjArrayKlass::cast(resolved_class)->bottom_klass(); + if (elem->is_instance_klass()) { + return is_class_resolution_deterministic(cp_holder, InstanceKlass::cast(elem)); + } else if (elem->is_typeArray_klass()) { + return true; + } + } else if (resolved_class->is_typeArray_klass()) { + return true; } return false; } void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { - constantPoolHandle cp(THREAD, ik->constants()); - if (cp->cache() == nullptr || cp->reference_map() == nullptr) { - // The cache may be null if the pool_holder klass fails verification - // at dump time due to missing dependencies. + if (!ik->is_linked()) { return; } - bool first_time; _processed_classes->put_if_absent(ik, &first_time); if (!first_time) { @@ -129,12 +166,9 @@ void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { return; } + constantPoolHandle cp(THREAD, ik->constants()); for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused switch (cp->tag_at(cp_index).value()) { - case JVM_CONSTANT_UnresolvedClass: - maybe_resolve_class(cp, cp_index, CHECK); - break; - case JVM_CONSTANT_String: resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings. break; @@ -142,43 +176,33 @@ void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { } } -Klass* ClassPrelinker::find_loaded_class(JavaThread* THREAD, oop class_loader, Symbol* name) { - HandleMark hm(THREAD); - Handle h_loader(THREAD, class_loader); - Klass* k = SystemDictionary::find_instance_or_array_klass(THREAD, name, +// This works only for the boot/platform/app loaders +Klass* ClassPrelinker::find_loaded_class(Thread* current, oop class_loader, Symbol* name) { + HandleMark hm(current); + Handle h_loader(current, class_loader); + Klass* k = SystemDictionary::find_instance_or_array_klass(current, name, h_loader, Handle()); if (k != nullptr) { return k; } - if (class_loader == SystemDictionary::java_system_loader()) { - return find_loaded_class(THREAD, SystemDictionary::java_platform_loader(), name); - } else if (class_loader == SystemDictionary::java_platform_loader()) { - return find_loaded_class(THREAD, nullptr, name); + if (h_loader() == SystemDictionary::java_system_loader()) { + return find_loaded_class(current, SystemDictionary::java_platform_loader(), name); + } else if (h_loader() == SystemDictionary::java_platform_loader()) { + return find_loaded_class(current, nullptr, name); + } else { + assert(h_loader() == nullptr, "This function only works for boot/platform/app loaders %p %p %p", + cast_from_oop
(h_loader()), + cast_from_oop
(SystemDictionary::java_system_loader()), + cast_from_oop
(SystemDictionary::java_platform_loader())); } return nullptr; } -Klass* ClassPrelinker::maybe_resolve_class(constantPoolHandle cp, int cp_index, TRAPS) { - assert(!is_in_archivebuilder_buffer(cp()), "sanity"); - InstanceKlass* cp_holder = cp->pool_holder(); - if (!cp_holder->is_shared_boot_class() && - !cp_holder->is_shared_platform_class() && - !cp_holder->is_shared_app_class()) { - // Don't trust custom loaders, as they may not be well-behaved - // when resolving classes. - return nullptr; - } - - Symbol* name = cp->klass_name_at(cp_index); - Klass* resolved_klass = find_loaded_class(THREAD, cp_holder->class_loader(), name); - if (resolved_klass != nullptr && can_archive_resolved_klass(cp_holder, resolved_klass)) { - Klass* k = cp->klass_at(cp_index, CHECK_NULL); // Should fail only with OOM - assert(k == resolved_klass, "must be"); - } - - return resolved_klass; +Klass* ClassPrelinker::find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index) { + Symbol* name = cp->klass_name_at(class_cp_index); + return find_loaded_class(current, cp->pool_holder()->class_loader(), name); } #if INCLUDE_CDS_JAVA_HEAP @@ -190,6 +214,110 @@ void ClassPrelinker::resolve_string(constantPoolHandle cp, int cp_index, TRAPS) } #endif +void ClassPrelinker::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) { + return; + } + + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + for (int cp_index = 1; cp_index < cp->length(); cp_index++) { + if (cp->tag_at(cp_index).value() == JVM_CONSTANT_UnresolvedClass) { + if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { + // This class was not resolved during trial run. Don't attempt to resolve it. Otherwise + // the compiler may generate less efficient code. + continue; + } + if (find_loaded_class(current, cp(), cp_index) == nullptr) { + // Do not resolve any class that has not been loaded yet + continue; + } + Klass* resolved_klass = cp->klass_at(cp_index, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } else { + log_trace(cds, resolve)("Resolved class [%3d] %s -> %s", cp_index, ik->external_name(), + resolved_klass->external_name()); + } + } + } +} + +void ClassPrelinker::preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + if (cp->cache() == nullptr) { + return; + } + for (int i = 0; i < ik->methods()->length(); i++) { + Method* m = ik->methods()->at(i); + BytecodeStream bcs(methodHandle(THREAD, m)); + while (!bcs.is_last_bytecode()) { + bcs.next(); + Bytecodes::Code raw_bc = bcs.raw_code(); + switch (raw_bc) { + case Bytecodes::_getfield: + case Bytecodes::_putfield: + maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } + break; + default: + break; + } + } + } +} + +void ClassPrelinker::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, + GrowableArray* preresolve_list, TRAPS) { + methodHandle mh(THREAD, m); + constantPoolHandle cp(THREAD, ik->constants()); + HandleMark hm(THREAD); + int cp_index = cp->to_cp_index(raw_index, bc); + + if (cp->is_resolved(raw_index, bc)) { + return; + } + + if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { + // This field wasn't resolved during the trial run. Don't attempt to resolve it. Otherwise + // the compiler may generate less efficient code. + return; + } + + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + if (find_loaded_class(THREAD, cp(), klass_cp_index) == nullptr) { + // Do not resolve any field/methods from a class that has not been loaded yet. + return; + } + + Klass* resolved_klass = cp->klass_ref_at(raw_index, bc, CHECK); + + switch (bc) { + case Bytecodes::_getfield: + case Bytecodes::_putfield: + InterpreterRuntime::resolve_get_put(bc, raw_index, mh, cp, false /*initialize_holder*/, CHECK); + break; + + default: + ShouldNotReachHere(); + } + + if (log_is_enabled(Trace, cds, resolve)) { + ResourceMark rm(THREAD); + bool resolved = cp->is_resolved(raw_index, bc); + Symbol* name = cp->name_ref_at(raw_index, bc); + Symbol* signature = cp->signature_ref_at(raw_index, bc); + log_trace(cds, resolve)("%s %s [%3d] %s -> %s.%s:%s", + (resolved ? "Resolved" : "Failed to resolve"), + Bytecodes::name(bc), cp_index, ik->external_name(), + resolved_klass->external_name(), + name->as_C_string(), signature->as_C_string()); + } +} + #ifdef ASSERT bool ClassPrelinker::is_in_archivebuilder_buffer(address p) { if (!Thread::current()->is_VM_thread() || ArchiveBuilder::current() == nullptr) { diff --git a/src/hotspot/share/cds/classPrelinker.hpp b/src/hotspot/share/cds/classPrelinker.hpp index 7a4f36386ea..41588961d8b 100644 --- a/src/hotspot/share/cds/classPrelinker.hpp +++ b/src/hotspot/share/cds/classPrelinker.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -25,6 +25,7 @@ #ifndef SHARE_CDS_CLASSPRELINKER_HPP #define SHARE_CDS_CLASSPRELINKER_HPP +#include "interpreter/bytecodes.hpp" #include "oops/oopsHierarchy.hpp" #include "memory/allStatic.hpp" #include "memory/allocation.hpp" @@ -64,14 +65,21 @@ class ClassPrelinker : AllStatic { return is_in_archivebuilder_buffer((address)(p)); } static void resolve_string(constantPoolHandle cp, int cp_index, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; - static Klass* maybe_resolve_class(constantPoolHandle cp, int cp_index, TRAPS); - static bool can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* resolved_klass); - static Klass* find_loaded_class(JavaThread* THREAD, oop class_loader, Symbol* name); + static bool is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class); + static Klass* find_loaded_class(Thread* current, oop class_loader, Symbol* name); + static Klass* find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index); + + // fmi = FieldRef/MethodRef/InterfaceMethodRef + static void maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, + GrowableArray* resolve_fmi_list, TRAPS); public: static void initialize(); static void dispose(); + static void preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); + static void preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); + // Is this class resolved as part of vmClasses::resolve_all()? If so, these // classes are guatanteed to be loaded at runtime (and cannot be replaced by JVMTI) // when CDS is enabled. Therefore, we can safely keep a direct reference to these @@ -82,10 +90,7 @@ public: // CDS archive. static void dumptime_resolve_constants(InstanceKlass* ik, TRAPS); - // Can we resolve the klass entry at cp_index in this constant pool, and store - // the result in the CDS archive? Returns true if cp_index is guaranteed to - // resolve to the same InstanceKlass* at both dump time and run time. - static bool can_archive_resolved_klass(ConstantPool* cp, int cp_index); + static bool is_resolution_deterministic(ConstantPool* cp, int cp_index); }; #endif // SHARE_CDS_CLASSPRELINKER_HPP diff --git a/src/hotspot/share/cds/dumpAllocStats.cpp b/src/hotspot/share/cds/dumpAllocStats.cpp index 21a40ca8f28..30ef1597063 100644 --- a/src/hotspot/share/cds/dumpAllocStats.cpp +++ b/src/hotspot/share/cds/dumpAllocStats.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 @@ -102,8 +102,12 @@ void DumpAllocStats::print_stats(int ro_all, int rw_all) { #undef fmt_stats - msg.debug("Class CP entries = %d, archived = %d (%3.1f%%)", - _num_klass_cp_entries, _num_klass_cp_entries_archived, - percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries)); - + msg.info("Class CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d", + _num_klass_cp_entries, _num_klass_cp_entries_archived, + percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries), + _num_klass_cp_entries_reverted); + msg.info("Field CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d", + _num_field_cp_entries, _num_field_cp_entries_archived, + percent_of(_num_field_cp_entries_archived, _num_field_cp_entries), + _num_field_cp_entries_reverted); } diff --git a/src/hotspot/share/cds/dumpAllocStats.hpp b/src/hotspot/share/cds/dumpAllocStats.hpp index f6e170cdef2..424a98aa738 100644 --- a/src/hotspot/share/cds/dumpAllocStats.hpp +++ b/src/hotspot/share/cds/dumpAllocStats.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 @@ -65,8 +65,12 @@ public: int _counts[2][_number_of_types]; int _bytes [2][_number_of_types]; + int _num_field_cp_entries; + int _num_field_cp_entries_archived; + int _num_field_cp_entries_reverted; int _num_klass_cp_entries; int _num_klass_cp_entries_archived; + int _num_klass_cp_entries_reverted; public: enum { RO = 0, RW = 1 }; @@ -74,8 +78,12 @@ public: DumpAllocStats() { memset(_counts, 0, sizeof(_counts)); memset(_bytes, 0, sizeof(_bytes)); - _num_klass_cp_entries = 0; - _num_klass_cp_entries_archived = 0; + _num_field_cp_entries = 0; + _num_field_cp_entries_archived = 0; + _num_field_cp_entries_reverted = 0; + _num_klass_cp_entries = 0; + _num_klass_cp_entries_archived = 0; + _num_klass_cp_entries_reverted = 0; }; CompactHashtableStats* symbol_stats() { return &_symbol_stats; } @@ -102,9 +110,16 @@ public: _bytes[RW][CppVTablesType] += byte_size; } - void record_klass_cp_entry(bool archived) { + void record_field_cp_entry(bool archived, bool reverted) { + _num_field_cp_entries ++; + _num_field_cp_entries_archived += archived ? 1 : 0; + _num_field_cp_entries_reverted += reverted ? 1 : 0; + } + + void record_klass_cp_entry(bool archived, bool reverted) { _num_klass_cp_entries ++; _num_klass_cp_entries_archived += archived ? 1 : 0; + _num_klass_cp_entries_reverted += reverted ? 1 : 0; } void print_stats(int ro_all, int rw_all); diff --git a/src/hotspot/share/cds/lambdaFormInvokers.hpp b/src/hotspot/share/cds/lambdaFormInvokers.hpp index 7bb5e5932c7..e78ddb1a1bc 100644 --- a/src/hotspot/share/cds/lambdaFormInvokers.hpp +++ b/src/hotspot/share/cds/lambdaFormInvokers.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 @@ -32,6 +32,7 @@ class ClassFileStream; template class Array; +class SerializeClosure; class LambdaFormInvokers : public AllStatic { private: @@ -46,5 +47,6 @@ class LambdaFormInvokers : public AllStatic { static void regenerate_holder_classes(TRAPS); static void serialize(SerializeClosure* soc); static void cleanup_regenerated_classes(); + inline static bool may_be_regenerated_class(Symbol* name); }; #endif // SHARE_CDS_LAMBDAFORMINVOKERS_HPP diff --git a/src/hotspot/share/cds/lambdaFormInvokers.inline.hpp b/src/hotspot/share/cds/lambdaFormInvokers.inline.hpp new file mode 100644 index 00000000000..dddd0e36c47 --- /dev/null +++ b/src/hotspot/share/cds/lambdaFormInvokers.inline.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 2024, 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. + * + */ + +#ifndef SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP +#define SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP + +#include "cds/lambdaFormInvokers.hpp" +#include "classfile/vmSymbols.hpp" + +inline bool LambdaFormInvokers::may_be_regenerated_class(Symbol* name) { + return name == vmSymbols::java_lang_invoke_Invokers_Holder() || + name == vmSymbols::java_lang_invoke_DirectMethodHandle_Holder() || + name == vmSymbols::java_lang_invoke_LambdaForm_Holder() || + name == vmSymbols::java_lang_invoke_DelegatingMethodHandle_Holder(); +} + +#endif // SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index dc5f2f5b637..fb779b039f4 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -645,27 +645,31 @@ JRT_END // void InterpreterRuntime::resolve_get_put(JavaThread* current, Bytecodes::Code bytecode) { - // resolve field - fieldDescriptor info; LastFrameAccessor last_frame(current); constantPoolHandle pool(current, last_frame.method()->constants()); methodHandle m(current, last_frame.method()); + + resolve_get_put(bytecode, last_frame.get_index_u2(bytecode), m, pool, true /*initialize_holder*/, current); +} + +void InterpreterRuntime::resolve_get_put(Bytecodes::Code bytecode, int field_index, + methodHandle& m, + constantPoolHandle& pool, + bool initialize_holder, TRAPS) { + fieldDescriptor info; bool is_put = (bytecode == Bytecodes::_putfield || bytecode == Bytecodes::_nofast_putfield || bytecode == Bytecodes::_putstatic); bool is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic); - int field_index = last_frame.get_index_u2(bytecode); { - JvmtiHideSingleStepping jhss(current); - JavaThread* THREAD = current; // For exception macros. + JvmtiHideSingleStepping jhss(THREAD); LinkResolver::resolve_field_access(info, pool, field_index, - m, bytecode, CHECK); + m, bytecode, initialize_holder, CHECK); } // end JvmtiHideSingleStepping // check if link resolution caused cpCache to be updated if (pool->resolved_field_entry_at(field_index)->is_resolved(bytecode)) return; - // compute auxiliary field attributes TosState state = as_TosState(info.field_type()); diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 297585d37e8..3a8db1363df 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 @@ -91,7 +91,12 @@ class InterpreterRuntime: AllStatic { static void throw_pending_exception(JavaThread* current); static void resolve_from_cache(JavaThread* current, Bytecodes::Code bytecode); - private: + + // Used by ClassListParser. + static void resolve_get_put(Bytecodes::Code bytecode, int field_index, + methodHandle& m, constantPoolHandle& pool, bool initialize_holder, TRAPS); + +private: // Statics & fields static void resolve_get_put(JavaThread* current, Bytecodes::Code bytecode); diff --git a/src/hotspot/share/interpreter/linkResolver.cpp b/src/hotspot/share/interpreter/linkResolver.cpp index 9247b8a1740..1fe715f9757 100644 --- a/src/hotspot/share/interpreter/linkResolver.cpp +++ b/src/hotspot/share/interpreter/linkResolver.cpp @@ -974,9 +974,14 @@ void LinkResolver::check_field_accessability(Klass* ref_klass, } } -void LinkResolver::resolve_field_access(fieldDescriptor& fd, const constantPoolHandle& pool, int index, const methodHandle& method, Bytecodes::Code byte, TRAPS) { +void LinkResolver::resolve_field_access(fieldDescriptor& fd, + const constantPoolHandle& pool, + int index, + const methodHandle& method, + Bytecodes::Code byte, + bool initialize_class, TRAPS) { LinkInfo link_info(pool, index, method, byte, CHECK); - resolve_field(fd, link_info, byte, true, CHECK); + resolve_field(fd, link_info, byte, initialize_class, CHECK); } void LinkResolver::resolve_field(fieldDescriptor& fd, diff --git a/src/hotspot/share/interpreter/linkResolver.hpp b/src/hotspot/share/interpreter/linkResolver.hpp index e18749cd6a5..80f5d230052 100644 --- a/src/hotspot/share/interpreter/linkResolver.hpp +++ b/src/hotspot/share/interpreter/linkResolver.hpp @@ -293,7 +293,16 @@ class LinkResolver: AllStatic { const constantPoolHandle& pool, int index, const methodHandle& method, - Bytecodes::Code byte, TRAPS); + Bytecodes::Code byte, + bool initialize_class, TRAPS); + static void resolve_field_access(fieldDescriptor& result, + const constantPoolHandle& pool, + int index, + const methodHandle& method, + Bytecodes::Code byte, TRAPS) { + resolve_field_access(result, pool, index, method, byte, + /* initialize_class*/true, THREAD); + } static void resolve_field(fieldDescriptor& result, const LinkInfo& link_info, Bytecodes::Code access_kind, bool initialize_class, TRAPS); diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 329b8157cb7..35a1d95b6e6 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -300,20 +300,22 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { objArrayOop rr = resolved_references(); if (rr != nullptr) { - ConstantPool* orig_pool = ArchiveBuilder::current()->get_source_addr(this); - objArrayOop scratch_rr = HeapShared::scratch_resolved_references(orig_pool); + int rr_len = rr->length(); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); + objArrayOop scratch_rr = HeapShared::scratch_resolved_references(src_cp); Array* ref_map = reference_map(); int ref_map_len = ref_map == nullptr ? 0 : ref_map->length(); - int rr_len = rr->length(); for (int i = 0; i < rr_len; i++) { oop obj = rr->obj_at(i); scratch_rr->obj_at_put(i, nullptr); - if (obj != nullptr && i < ref_map_len) { - int index = object_to_cp_index(i); - if (tag_at(index).is_string()) { - assert(java_lang_String::is_instance(obj), "must be"); - if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) { - scratch_rr->obj_at_put(i, obj); + if (obj != nullptr) { + if (i < ref_map_len) { + int index = object_to_cp_index(i); + if (tag_at(index).is_string()) { + assert(java_lang_String::is_instance(obj), "must be"); + if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) { + scratch_rr->obj_at_put(i, obj); + } } } } @@ -386,23 +388,61 @@ void ConstantPool::remove_unshareable_info() { // we always set _on_stack to true to avoid having to change _flags during runtime. _flags |= (_on_stack | _is_shared); - if (!_pool_holder->is_linked() && !_pool_holder->verified_at_dump_time()) { - return; + // resolved_references(): remember its length. If it cannot be restored + // from the archived heap objects at run time, we need to dynamically allocate it. + if (cache() != nullptr) { + set_resolved_reference_length( + resolved_references() != nullptr ? resolved_references()->length() : 0); + set_resolved_references(OopHandle()); } - // Resolved references are not in the shared archive. - // Save the length for restoration. It is not necessarily the same length - // as reference_map.length() if invokedynamic is saved. It is needed when - // re-creating the resolved reference array if archived heap data cannot be map - // at runtime. - set_resolved_reference_length( - resolved_references() != nullptr ? resolved_references()->length() : 0); - set_resolved_references(OopHandle()); + remove_unshareable_entries(); +} - bool archived = false; +static const char* get_type(Klass* k) { + const char* type; + Klass* src_k; + if (ArchiveBuilder::is_active() && ArchiveBuilder::current()->is_in_buffer_space(k)) { + src_k = ArchiveBuilder::current()->get_source_addr(k); + } else { + src_k = k; + } + + if (src_k->is_objArray_klass()) { + src_k = ObjArrayKlass::cast(src_k)->bottom_klass(); + assert(!src_k->is_objArray_klass(), "sanity"); + } + + if (src_k->is_typeArray_klass()) { + type = "prim"; + } else { + InstanceKlass* src_ik = InstanceKlass::cast(src_k); + oop loader = src_ik->class_loader(); + if (loader == nullptr) { + type = "boot"; + } else if (loader == SystemDictionary::java_platform_loader()) { + type = "plat"; + } else if (loader == SystemDictionary::java_system_loader()) { + type = "app"; + } else { + type = "unreg"; + } + } + + return type; +} + +void ConstantPool::remove_unshareable_entries() { + ResourceMark rm; + log_info(cds, resolve)("Archiving CP entries for %s", pool_holder()->name()->as_C_string()); for (int cp_index = 1; cp_index < length(); cp_index++) { // cp_index 0 is unused - switch (tag_at(cp_index).value()) { + int cp_tag = tag_at(cp_index).value(); + switch (cp_tag) { + case JVM_CONSTANT_UnresolvedClass: + ArchiveBuilder::alloc_stats()->record_klass_cp_entry(false, false); + break; case JVM_CONSTANT_UnresolvedClassInError: tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass); + ArchiveBuilder::alloc_stats()->record_klass_cp_entry(false, true); break; case JVM_CONSTANT_MethodHandleInError: tag_at_put(cp_index, JVM_CONSTANT_MethodHandle); @@ -414,8 +454,9 @@ void ConstantPool::remove_unshareable_info() { tag_at_put(cp_index, JVM_CONSTANT_Dynamic); break; case JVM_CONSTANT_Class: - archived = maybe_archive_resolved_klass_at(cp_index); - ArchiveBuilder::alloc_stats()->record_klass_cp_entry(archived); + remove_resolved_klass_if_non_deterministic(cp_index); + break; + default: break; } } @@ -426,40 +467,46 @@ void ConstantPool::remove_unshareable_info() { } } -bool ConstantPool::maybe_archive_resolved_klass_at(int cp_index) { +void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) { assert(ArchiveBuilder::current()->is_in_buffer_space(this), "must be"); assert(tag_at(cp_index).is_klass(), "must be resolved"); - if (pool_holder()->is_hidden() && cp_index == pool_holder()->this_class_index()) { - // All references to a hidden class's own field/methods are through this - // index, which was resolved in ClassFileParser::fill_instance_klass. We - // must preserve it. - return true; + Klass* k = resolved_klass_at(cp_index); + bool can_archive; + + if (k == nullptr) { + // We'd come here if the referenced class has been excluded via + // SystemDictionaryShared::is_excluded_class(). As a result, ArchiveBuilder + // has cleared the resolved_klasses()->at(...) pointer to NULL. Thus, we + // need to revert the tag to JVM_CONSTANT_UnresolvedClass. + can_archive = false; + } else { + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); + can_archive = ClassPrelinker::is_resolution_deterministic(src_cp, cp_index); } - CPKlassSlot kslot = klass_slot_at(cp_index); - int resolved_klass_index = kslot.resolved_klass_index(); - Klass* k = resolved_klasses()->at(resolved_klass_index); - // k could be null if the referenced class has been excluded via - // SystemDictionaryShared::is_excluded_class(). + if (!can_archive) { + int resolved_klass_index = klass_slot_at(cp_index).resolved_klass_index(); + resolved_klasses()->at_put(resolved_klass_index, nullptr); + tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass); + } - if (k != nullptr) { - ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); - if (ClassPrelinker::can_archive_resolved_klass(src_cp, cp_index)) { - if (log_is_enabled(Debug, cds, resolve)) { - ResourceMark rm; - log_debug(cds, resolve)("Resolved klass CP entry [%d]: %s => %s", cp_index, - pool_holder()->external_name(), k->external_name()); - } - return true; + LogStreamHandle(Trace, cds, resolve) log; + if (log.is_enabled()) { + ResourceMark rm; + log.print("%s klass CP entry [%3d]: %s %s", + (can_archive ? "archived" : "reverted"), + cp_index, pool_holder()->name()->as_C_string(), get_type(pool_holder())); + if (can_archive) { + log.print(" => %s %s%s", k->name()->as_C_string(), get_type(k), + (!k->is_instance_klass() || pool_holder()->is_subtype_of(k)) ? "" : " (not supertype)"); + } else { + Symbol* name = klass_name_at(cp_index); + log.print(" %s", name->as_C_string()); } } - // This referenced class cannot be archived. Revert the tag to UnresolvedClass, - // so that the proper class loading and initialization can happen at runtime. - resolved_klasses()->at_put(resolved_klass_index, nullptr); - tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass); - return false; + ArchiveBuilder::alloc_stats()->record_klass_cp_entry(can_archive, /*reverted=*/!can_archive); } #endif // INCLUDE_CDS @@ -707,6 +754,31 @@ int ConstantPool::to_cp_index(int index, Bytecodes::Code code) { } } +bool ConstantPool::is_resolved(int index, Bytecodes::Code code) { + assert(cache() != nullptr, "'index' is a rewritten index so this class must have been rewritten"); + switch(code) { + case Bytecodes::_invokedynamic: + return resolved_indy_entry_at(index)->is_resolved(); + + case Bytecodes::_getfield: + case Bytecodes::_getstatic: + case Bytecodes::_putfield: + case Bytecodes::_putstatic: + return resolved_field_entry_at(index)->is_resolved(code); + + case Bytecodes::_invokeinterface: + case Bytecodes::_invokehandle: + case Bytecodes::_invokespecial: + case Bytecodes::_invokestatic: + case Bytecodes::_invokevirtual: + case Bytecodes::_fast_invokevfinal: // Bytecode interpreter uses this + return resolved_method_entry_at(index)->is_resolved(code); + + default: + fatal("Unexpected bytecode: %s", Bytecodes::name(code)); + } +} + u2 ConstantPool::uncached_name_and_type_ref_index_at(int cp_index) { if (tag_at(cp_index).has_bootstrap()) { u2 pool_index = bootstrap_name_and_type_ref_index_at(cp_index); diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index e48229749f3..7a17c62ddaf 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -661,6 +661,8 @@ class ConstantPool : public Metadata { int to_cp_index(int which, Bytecodes::Code code); + bool is_resolved(int which, Bytecodes::Code code); + // Lookup for entries consisting of (name_index, signature_index) u2 name_ref_index_at(int cp_index); // == low-order jshort of name_and_type_at(cp_index) u2 signature_ref_index_at(int cp_index); // == high-order jshort of name_and_type_at(cp_index) @@ -677,9 +679,11 @@ class ConstantPool : public Metadata { // CDS support objArrayOop prepare_resolved_references_for_archiving() NOT_CDS_JAVA_HEAP_RETURN_(nullptr); void add_dumped_interned_strings() NOT_CDS_JAVA_HEAP_RETURN; - bool maybe_archive_resolved_klass_at(int cp_index); void remove_unshareable_info(); void restore_unshareable_info(TRAPS); +private: + void remove_unshareable_entries(); + void remove_resolved_klass_if_non_deterministic(int cp_index); #endif private: diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 03dbce19f2e..c16ba3b76f7 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" +#include "cds/classPrelinker.hpp" #include "cds/heapShared.hpp" #include "classfile/resolutionErrors.hpp" #include "classfile/systemDictionary.hpp" @@ -388,18 +389,14 @@ void ConstantPoolCache::record_gc_epoch() { #if INCLUDE_CDS void ConstantPoolCache::remove_unshareable_info() { assert(CDSConfig::is_dumping_archive(), "sanity"); - // is the copy to be written into the archive. It's in the ArchiveBuilder's "buffer space". - // However, this->_initial_entries was not copied/relocated by the ArchiveBuilder, so it's - // still pointing to the array allocated inside save_for_archive(). + if (_resolved_indy_entries != nullptr) { for (int i = 0; i < _resolved_indy_entries->length(); i++) { resolved_indy_entry_at(i)->remove_unshareable_info(); } } if (_resolved_field_entries != nullptr) { - for (int i = 0; i < _resolved_field_entries->length(); i++) { - resolved_field_entry_at(i)->remove_unshareable_info(); - } + remove_resolved_field_entries_if_non_deterministic(); } if (_resolved_method_entries != nullptr) { for (int i = 0; i < _resolved_method_entries->length(); i++) { @@ -407,6 +404,41 @@ void ConstantPoolCache::remove_unshareable_info() { } } } + +void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() { + ConstantPool* cp = constant_pool(); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(cp); + for (int i = 0; i < _resolved_field_entries->length(); i++) { + ResolvedFieldEntry* rfi = _resolved_field_entries->adr_at(i); + int cp_index = rfi->constant_pool_index(); + bool archived = false; + bool resolved = rfi->is_resolved(Bytecodes::_getfield) || + rfi->is_resolved(Bytecodes::_putfield); + if (resolved && ClassPrelinker::is_resolution_deterministic(src_cp, cp_index)) { + rfi->mark_and_relocate(); + archived = true; + } else { + rfi->remove_unshareable_info(); + } + if (resolved) { + LogStreamHandle(Trace, cds, resolve) log; + if (log.is_enabled()) { + ResourceMark rm; + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + Symbol* klass_name = cp->klass_name_at(klass_cp_index); + Symbol* name = cp->uncached_name_ref_at(cp_index); + Symbol* signature = cp->uncached_signature_ref_at(cp_index); + log.print("%s field CP entry [%3d]: %s %s %s.%s:%s", + (archived ? "archived" : "reverted"), + cp_index, + cp->pool_holder()->name()->as_C_string(), + (archived ? "=>" : " "), + klass_name->as_C_string(), name->as_C_string(), signature->as_C_string()); + } + } + ArchiveBuilder::alloc_stats()->record_field_cp_entry(archived, resolved && !archived); + } +} #endif // INCLUDE_CDS void ConstantPoolCache::deallocate_contents(ClassLoaderData* data) { diff --git a/src/hotspot/share/oops/cpCache.hpp b/src/hotspot/share/oops/cpCache.hpp index f9239835198..1f91b194623 100644 --- a/src/hotspot/share/oops/cpCache.hpp +++ b/src/hotspot/share/oops/cpCache.hpp @@ -193,14 +193,12 @@ class ConstantPoolCache: public MetaspaceObj { #if INCLUDE_CDS void remove_unshareable_info(); - void save_for_archive(TRAPS); #endif public: static int size() { return align_metadata_size(sizeof(ConstantPoolCache) / wordSize); } private: - // Helpers ConstantPool** constant_pool_addr() { return &_constant_pool; } @@ -224,6 +222,10 @@ class ConstantPoolCache: public MetaspaceObj { void dump_cache(); #endif // INCLUDE_JVMTI +#if INCLUDE_CDS + void remove_resolved_field_entries_if_non_deterministic(); +#endif + // RedefineClasses support DEBUG_ONLY(bool on_stack() { return false; }) void deallocate_contents(ClassLoaderData* data); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 83dde12af3d..8a716c8f9f6 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2557,7 +2557,9 @@ void InstanceKlass::remove_unshareable_info() { init_implementor(); } - constants()->remove_unshareable_info(); + // Call remove_unshareable_info() on other objects that belong to this class, except + // for constants()->remove_unshareable_info(), which is called in a separate pass in + // ArchiveBuilder::make_klasses_shareable(), for (int i = 0; i < methods()->length(); i++) { Method* m = methods()->at(i); diff --git a/src/hotspot/share/oops/resolvedFieldEntry.cpp b/src/hotspot/share/oops/resolvedFieldEntry.cpp index 779f7676293..83243251306 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.cpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -23,7 +23,8 @@ */ #include "precompiled.hpp" -#include "resolvedFieldEntry.hpp" +#include "cds/archiveBuilder.hpp" +#include "oops/resolvedFieldEntry.hpp" void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr("Field Entry:"); @@ -43,8 +44,14 @@ void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr(" - Put Bytecode: %s", Bytecodes::name((Bytecodes::Code)put_code())); } +#if INCLUDE_CDS void ResolvedFieldEntry::remove_unshareable_info() { u2 saved_cpool_index = _cpool_index; memset(this, 0, sizeof(*this)); _cpool_index = saved_cpool_index; } + +void ResolvedFieldEntry::mark_and_relocate() { + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_field_holder); +} +#endif diff --git a/src/hotspot/share/oops/resolvedFieldEntry.hpp b/src/hotspot/share/oops/resolvedFieldEntry.hpp index 7765240926d..c98d5f54d1e 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.hpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -55,6 +55,17 @@ class ResolvedFieldEntry { u1 _flags; // Flags: [0000|00|is_final|is_volatile] u1 _get_code, _put_code; // Get and Put bytecodes of the field + void copy_from(const ResolvedFieldEntry& other) { + _field_holder = other._field_holder; + _field_offset = other._field_offset; + _field_index = other._field_index; + _cpool_index = other._cpool_index; + _tos_state = other._tos_state; + _flags = other._flags; + _get_code = other._get_code; + _put_code = other._put_code; + } + public: ResolvedFieldEntry(u2 cpi) : _field_holder(nullptr), @@ -65,9 +76,19 @@ public: _flags(0), _get_code(0), _put_code(0) {} + ResolvedFieldEntry() : ResolvedFieldEntry(0) {} + ResolvedFieldEntry(const ResolvedFieldEntry& other) { + copy_from(other); + } + + ResolvedFieldEntry& operator=(const ResolvedFieldEntry& other) { + copy_from(other); + return *this; + } + // Bit shift to get flags // Note: Only two flags exists at the moment but more could be added enum { @@ -131,7 +152,10 @@ public: } // CDS +#if INCLUDE_CDS void remove_unshareable_info(); + void mark_and_relocate(); +#endif // Offsets static ByteSize field_holder_offset() { return byte_offset_of(ResolvedFieldEntry, _field_holder); } diff --git a/src/hotspot/share/oops/resolvedIndyEntry.cpp b/src/hotspot/share/oops/resolvedIndyEntry.cpp index eff543a5448..93ba3d6916c 100644 --- a/src/hotspot/share/oops/resolvedIndyEntry.cpp +++ b/src/hotspot/share/oops/resolvedIndyEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/archiveBuilder.hpp" #include "code/compressedStream.hpp" #include "oops/method.hpp" #include "oops/resolvedIndyEntry.hpp" @@ -37,6 +38,7 @@ bool ResolvedIndyEntry::check_no_old_or_obsolete_entry() { } } +#if INCLUDE_CDS void ResolvedIndyEntry::remove_unshareable_info() { u2 saved_resolved_references_index = _resolved_references_index; u2 saved_cpool_index = _cpool_index; @@ -45,6 +47,12 @@ void ResolvedIndyEntry::remove_unshareable_info() { _cpool_index = saved_cpool_index; } +void ResolvedIndyEntry::mark_and_relocate() { + assert(is_resolved(), "must be"); + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_method); +} +#endif + void ResolvedIndyEntry::print_on(outputStream* st) const { st->print_cr("Resolved InvokeDynamic Info:"); if (_method != nullptr) { diff --git a/src/hotspot/share/oops/resolvedIndyEntry.hpp b/src/hotspot/share/oops/resolvedIndyEntry.hpp index 25797d338c4..3d7d3893311 100644 --- a/src/hotspot/share/oops/resolvedIndyEntry.hpp +++ b/src/hotspot/share/oops/resolvedIndyEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -129,7 +129,10 @@ public: bool check_no_old_or_obsolete_entry(); // CDS +#if INCLUDE_CDS void remove_unshareable_info(); + void mark_and_relocate(); +#endif // Offsets static ByteSize method_offset() { return byte_offset_of(ResolvedIndyEntry, _method); } diff --git a/src/hotspot/share/oops/resolvedMethodEntry.cpp b/src/hotspot/share/oops/resolvedMethodEntry.cpp index 7702dddd9cc..9564dbbcdc4 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.cpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/archiveBuilder.hpp" #include "oops/method.hpp" #include "oops/resolvedMethodEntry.hpp" @@ -50,10 +51,23 @@ void ResolvedMethodEntry::reset_entry() { } } +#if INCLUDE_CDS void ResolvedMethodEntry::remove_unshareable_info() { reset_entry(); } +void ResolvedMethodEntry::mark_and_relocate(ConstantPool* src_cp) { + if (_method == nullptr) { + assert(bytecode2() == Bytecodes::_invokevirtual, ""); + } else { + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_method); + } + if (bytecode1() == Bytecodes::_invokeinterface) { + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_entry_specific._interface_klass); + } +} +#endif + void ResolvedMethodEntry::print_on(outputStream* st) const { st->print_cr("Method Entry:"); diff --git a/src/hotspot/share/oops/resolvedMethodEntry.hpp b/src/hotspot/share/oops/resolvedMethodEntry.hpp index f5f7b8fbab5..e8445223600 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.hpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -224,7 +224,10 @@ class ResolvedMethodEntry { void reset_entry(); // CDS +#if INCLUDE_CDS void remove_unshareable_info(); + void mark_and_relocate(ConstantPool* src_cp); +#endif // Offsets static ByteSize klass_offset() { return byte_offset_of(ResolvedMethodEntry, _entry_specific._interface_klass); } diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 500036febab..9588f32e6b3 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -3729,7 +3729,7 @@ JVM_ENTRY(void, JVM_LogLambdaFormInvoker(JNIEnv *env, jstring line)) } if (ClassListWriter::is_enabled()) { ClassListWriter w; - w.stream()->print_cr("%s %s", LAMBDA_FORM_TAG, c_line); + w.stream()->print_cr("%s %s", ClassListParser::lambda_form_tag(), c_line); } } #endif // INCLUDE_CDS diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index f4d9152c8a5..d3e76364a03 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "cds/cds_globals.hpp" +#include "cds/classListWriter.hpp" #include "cds/dynamicArchive.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" @@ -433,6 +434,10 @@ void before_exit(JavaThread* thread, bool halt) { } #endif +#if INCLUDE_CDS + ClassListWriter::write_resolved_constants(); +#endif + // Hang forever on exit if we're reporting an error. if (ShowMessageBoxOnError && VMError::is_error_reported()) { os::infinite_sleep(); diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index ecded09f4cc..101cbe76afd 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -448,6 +448,7 @@ hotspot_appcds_dynamic = \ -runtime/cds/appcds/lambdaForm/DefaultClassListLFInvokers.java \ -runtime/cds/appcds/methodHandles \ -runtime/cds/appcds/sharedStrings \ + -runtime/cds/appcds/resolvedConstants \ -runtime/cds/appcds/ArchiveRelocationTest.java \ -runtime/cds/appcds/BadBSM.java \ -runtime/cds/appcds/DumpClassList.java \ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java new file mode 100644 index 00000000000..96744b546a8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedConstants.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, 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 + * @summary Dump time resolutiom of constant pool entries. + * @requires vm.cds + * @requires vm.compMode != "Xcomp" + * @library /test/lib + * @build ResolvedConstants + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar ResolvedConstantsApp ResolvedConstantsFoo ResolvedConstantsBar + * @run driver ResolvedConstants + */ + +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; + +public class ResolvedConstants { + static final String classList = "ResolvedConstants.classlist"; + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = ResolvedConstantsApp.class.getName(); + + public static void main(String[] args) throws Exception { + // dump class list + CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Hello ResolvedConstantsApp"); + }); + + CDSOptions opts = (new CDSOptions()) + .addPrefix("-XX:ExtraSharedClassListFile=" + classList, + "-cp", appJar, + "-Xlog:cds+resolve=trace"); + CDSTestUtils.createArchiveAndCheck(opts) + // Always resolve reference when a class references itself + .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => ResolvedConstantsApp app") + + // Always resolve reference when a class references a super class + .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => java/lang/Object boot") + .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsBar app => ResolvedConstantsFoo app") + + // Always resolve reference when a class references a super interface + .shouldMatch("cds,resolve.*archived klass.* ResolvedConstantsApp app => java/lang/Runnable boot") + + // java/lang/System is in the root loader but ResolvedConstantsApp is loaded by the app loader. + // Even though System is in the vmClasses list, when ResolvedConstantsApp looks up + // "java/lang/System" in its ConstantPool, the app loader may not have resolved the System + // class yet (i.e., there's no initiaited class entry for System in the app loader's dictionary) + .shouldMatch("cds,resolve.*reverted klass.* ResolvedConstantsApp .*java/lang/System") + + // Always resolve references to fields in the current class or super class(es) + .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsBar.b:I") + .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsBar.a:I") + .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsBar => ResolvedConstantsFoo.a:I") + + // Do not resolve field references to child classes + .shouldMatch("cds,resolve.*archived field.* ResolvedConstantsFoo => ResolvedConstantsFoo.a:I") + .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsFoo ResolvedConstantsBar.a:I") + .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsFoo ResolvedConstantsBar.b:I") + + + // Do not resolve field references to unrelated classes + .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsApp ResolvedConstantsBar.a:I") + .shouldMatch("cds,resolve.*reverted field.* ResolvedConstantsApp ResolvedConstantsBar.b:I") + + ; + } +} + +class ResolvedConstantsApp implements Runnable { + public static void main(String args[]) { + System.out.println("Hello ResolvedConstantsApp"); + Object a = new ResolvedConstantsApp(); + ((Runnable)a).run(); + + ResolvedConstantsFoo foo = new ResolvedConstantsFoo(); + ResolvedConstantsBar bar = new ResolvedConstantsBar(); + bar.a ++; + bar.b ++; + bar.doit(); + } + public void run() {} +} + +class ResolvedConstantsFoo { + int a = 1; + void doit() { + } + + void doBar(ResolvedConstantsBar bar) { + bar.a ++; + bar.b ++; + } +} + +class ResolvedConstantsBar extends ResolvedConstantsFoo { + int b = 2; + void doit() { + System.out.println("Hello ResolvedConstantsBar and " + ResolvedConstantsFoo.class.getName()); + System.out.println("a = " + a); + System.out.println("a = " + ((ResolvedConstantsFoo)this).a); + System.out.println("b = " + b); + + doBar(this); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedPutField.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedPutField.java new file mode 100644 index 00000000000..250ff7d7d6f --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedPutField.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024, 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 + * @summary Fieldref entry for putfield bytecodes for a final field cannot be preresolved if it's used by a + * method outside of + * @requires vm.cds + * @requires vm.compMode != "Xcomp" + * @library /test/lib + * @build ResolvedPutFieldHelper + * @build ResolvedPutField + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar ResolvedPutFieldApp ResolvedPutFieldHelper + * @run driver ResolvedPutField + */ + +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; + +public class ResolvedPutField { + static final String classList = "ResolvedPutField.classlist"; + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = ResolvedPutFieldApp.class.getName(); + static final String error = "Update to non-static final field ResolvedPutFieldHelper.x attempted from a different method (set_x) than the initializer method "; + public static void main(String[] args) throws Exception { + // dump class list + CDSTestUtils.dumpClassList(classList, "-cp", appJar, mainClass) + .assertNormalExit(error); + + CDSOptions opts = (new CDSOptions()) + .addPrefix("-XX:ExtraSharedClassListFile=" + classList, + "-cp", appJar, + "-Xlog:cds+resolve=trace"); + CDSTestUtils.createArchiveAndCheck(opts) + .shouldMatch("cds,resolve.*Failed to resolve putfield .*ResolvedPutFieldHelper -> ResolvedPutFieldHelper.x:I"); + } +} + +class ResolvedPutFieldApp { + public static void main(String args[]) { + try { + ResolvedPutFieldHelper.main(args); + } catch (IllegalAccessError e) { + System.out.println("IllegalAccessError expected:"); + System.out.println(e); + System.exit(0); + } + throw new RuntimeException("IllegalAccessError expected!"); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedPutFieldHelper.jasm b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedPutFieldHelper.jasm new file mode 100644 index 00000000000..dc7bcfff050 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/ResolvedPutFieldHelper.jasm @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, 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. + * + */ + + +/* + +class ResolvedPutFieldHelper { + int x; // change it to 'final' + ResolvedPutFieldHelper() { x = 1; } + void set_x() { x = 2; } + + public static void main(String args[]) { + ResolvedPutFieldHelper s = new ResolvedPutFieldHelper(); + s.set_x(); + System.out.println(s.x); + } +} + +*/ + + + +super class ResolvedPutFieldHelper + version 66:0 +{ + //WAS Field x:I; + final Field x:I; + + Method "":"()V" + stack 2 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + aload_0; + iconst_1; + putfield Field x:"I"; + return; + } + + // set_x is not allowed to write to the final "x" field. If CDS pre-resolves its + // ResolvedFieldEntry for the putfield bytecode, then we cannot get + // the IllegalAccessError at runtime. See JDK-8157181 for the code that + // throws the IllegalAccessError. + + Method set_x:"()V" + stack 2 locals 1 + { + aload_0; + iconst_2; + putfield Field x:"I"; + return; + } + public static Method main:"([Ljava/lang/String;)V" + stack 2 locals 2 + { + new class ResolvedPutFieldHelper; + dup; + invokespecial Method "":"()V"; + astore_1; + aload_1; + invokevirtual Method set_x:"()V"; + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + aload_1; + getfield Field x:"I"; + invokevirtual Method java/io/PrintStream.println:"(I)V"; + return; + } +}