Skip to content

8352750: Use jimage for preview enabled value classes #1409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: lworld
Choose a base branch
from

Conversation

david-beaumont
Copy link
Contributor

@david-beaumont david-beaumont commented Mar 26, 2025

Draft pull request. Final description TBD.


Progress

  • Change must not contain extraneous whitespace

Integration blocker

 ⚠️ Title mismatch between PR and JBS for issue JDK-8352750

Issue

  • JDK-8352750: [lworld] Use jimage for preview enabled value classes (Enhancement - P3) ⚠️ Title mismatch between PR and JBS.

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/valhalla.git pull/1409/head:pull/1409
$ git checkout pull/1409

Update a local copy of the PR:
$ git checkout pull/1409
$ git pull https://git.openjdk.org/valhalla.git pull/1409/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1409

View PR using the GUI difftool:
$ git pr show -t 1409

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/valhalla/pull/1409.diff

@@ -97,9 +97,18 @@ static JImageClose_t JImageClose = nullptr;
static JImageFindResource_t JImageFindResource = nullptr;
static JImageGetResource_t JImageGetResource = nullptr;

// JimageFile pointer, or null if exploded JDK build.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo fix.

@bridgekeeper
Copy link

bridgekeeper bot commented Mar 26, 2025

👋 Welcome back david-beaumont! A progress list of the required criteria for merging this PR into lworld will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Mar 26, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

JIMAGE_MODE_ENABLE_PREVIEW = 2
};
static JImageMode JImage_mode = JIMAGE_MODE_UNINITIALIZED;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private static state. I don't like it being static, but everything else in ClassLoader is, so I figured put it next to the thing it affects.

@@ -154,7 +163,7 @@ void ClassLoader::print_counters(outputStream *st) {

GrowableArray<ModuleClassPathList*>* ClassLoader::_patch_mod_entries = nullptr;
GrowableArray<ModuleClassPathList*>* ClassLoader::_exploded_entries = nullptr;
ClassPathEntry* ClassLoader::_jrt_entry = nullptr;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reflect the true type of _jrt_entry (this is useful later).

JImageFile* ClassPathImageEntry::jimage_non_null() const {
assert(ClassLoader::has_jrt_entry(), "must be");
assert(jimage() != nullptr, "should have been opened by ClassLoader::lookup_vm_options "
assert(JImage_file != nullptr, "should have been opened by ClassLoader::lookup_vm_options "
"and remained throughout normal JVM lifetime");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using the field directly in these 4 places where jimage() used to be called, we remove a public method from ClassPathEntry, which greatly improves encapsulation, which is important for ensuring JImage use is consistent with respect to --enable-preview.


bool ClassPathImageEntry::is_preview_enabled() const {
assert(JImage_mode != JIMAGE_MODE_UNINITIALIZED, "uninitialized JImage reference");
return JImage_mode == JIMAGE_MODE_ENABLE_PREVIEW;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper function to encapsulate the static field read and ensure it's only used inside ClassPathImageEntry.
While other code could directly access JImage_mode, it's easier to audit when going through this function (and would be easier to refactor later if it stops being a static field). It also adds a guard to prevent use-before-init.

@@ -389,6 +399,8 @@ ClassPathImageEntry::ClassPathImageEntry(JImageFile* jimage, const char* name) :
ClassPathEntry() {
guarantee(jimage != nullptr, "jimage file is null");
guarantee(name != nullptr, "jimage file name is null");
guarantee(JImage_mode != JIMAGE_MODE_UNINITIALIZED, "jimage is uninitialized");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this a guarantee to match the null pointer checks above (this should be the only place, other than is_preview_enabled() where JImage_mode is read.

@@ -407,16 +419,19 @@ ClassFileStream* ClassPathImageEntry::open_stream(JavaThread* current, const cha
// 2. A package is in at most one module in the jimage file.
//
ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current, const char* name, ClassLoaderData* loader_data) {
JImageFile* jimage = this->jimage_non_null();
bool is_preview = this->is_preview_enabled();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read related fields together and only once. Now this is the only place where either of these fields are obtained (and guards are present to ensure it happens after final initialization of flags) we know it's the only bit of code that needs modifying later to handle is_preview. For now the flag is ignored however.

jlong size;
JImageLocationRef location = (*JImageFindResource)(jimage_non_null(), "", get_jimage_version_string(), name, &size);
JImageLocationRef location = (*JImageFindResource)(jimage, "", get_jimage_version_string(), name, &size);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than call the function multiple times, it seemed clearer to read it (and the new flag) just once at the start of this function.

_jrt_entry = new ClassPathImageEntry(JImage_file, canonical_path);
assert(_jrt_entry != nullptr && _jrt_entry->is_modules_image(), "No java runtime image present");
assert(_jrt_entry->jimage() != nullptr, "No java runtime image");
} // else it's an exploded build.
Copy link
Contributor Author

@david-beaumont david-beaumont Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This final assert is very very redundant given the asserts around it and in the ClassPathImageEntry constructor. I removed it because it is otherwise a call to the old jimage() method outside the ClassPathImageEntry namespace (which means I would need to leave a public method that returns the JImageFile*, which I think is quite bad. The actual call to jimage() would just be reading the static field anyway, which we already know isn't null from (original) line 676.

JImage_mode = enable_preview ? JIMAGE_MODE_ENABLE_PREVIEW : JIMAGE_MODE_DEFAULT;
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Called from arguments.cpp after flags are parsed.

@@ -107,19 +105,29 @@ class ClassPathZipEntry: public ClassPathEntry {
};


// For java image files
// A singleton path entry which takes ownership of the initialized JImageFile
// reference. Not used for exploded builds.
class ClassPathImageEntry: public ClassPathEntry {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some comments while working since there is subtlety here and things weren't very clear to me. It's important, for example, to know that this is explicitly intended as a singleton instance.

// Returns the non-null, initialized JImage file reference.
JImageFile* jimage_non_null() const;
// Returns whether the JImage file will return resources suitable for a preview JVM.
bool is_preview_enabled() const;
public:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having these functions private guarantees nobody else can read (and potentially) misuse the JImageFile* outside the ClassPathImageEntry class. This is very important for improving encapsulation, which was somewhat lacking before.

public:
bool is_modules_image() const;
const char* name() const { return _name == nullptr ? "" : _name; }
JImageFile* jimage() const;
JImageFile* jimage_non_null() const;
// Called to closes the JImage during os::abort (normally not called).
void close_jimage();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added here because it's otherwise confusing to see a "close" method in a C++ object, because you naturally expect a destructor to be used. This is optional, but I think it helps people understand that this isn't "normal" semantics.

@@ -331,7 +339,7 @@ class ClassLoader: AllStatic {

// Modular java runtime image is present vs. a build with exploded modules
static bool has_jrt_entry() { return (_jrt_entry != nullptr); }
static ClassPathEntry* get_jrt_entry() { return _jrt_entry; }
static ClassPathImageEntry* get_jrt_entry() { return _jrt_entry; }
static void close_jrt_image();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a slight break in encapsulation since it's called from outside classLoader.cpp (in filemap.cpp). I could change it back to ClassPathEntry* if desired, but leave the static field type as ClassPathImageEntry*. Nobody calling this from outside cares about the type.

@@ -63,8 +63,6 @@ class ClassPathEntry : public CHeapObj<mtClass> {
// Is this entry created from the "Class-path" attribute from a JAR Manifest?
virtual bool from_class_path_attr() const { return false; }
virtual const char* name() const = 0;
virtual JImageFile* jimage() const { return nullptr; }
virtual void close_jimage() {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but this provides close_jimage for ClassPathZipEntry; though it is a no-op, as usual for zip files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it seems nobody calls it virtually and everything compiles fine without it, so I'm calling that "dead code".
Much better encapsulation without this.

Comment on lines 2017 to 2022
// if (err != nullptr) {
// log_warning(cds)("This archive was %s with --enable-preview -XX:+EnableValhalla. It is "
// "incompatible with the current JVM setting", err);
// return false;
// }
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code should be restored. It should operate correctly (with other fixes in place).

@@ -66,7 +66,7 @@ endif
# default classlist is minimal, let's filter out the '@cp' lines until we can
# find a proper solution.
CLASSLIST_FILE_VM_OPTS = \
-Duser.language=en -Duser.country=US
-Duser.language=en -Duser.country=US -Dvalue.bsm.debug=true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed.

@@ -77,7 +77,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST
$(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:[email protected] \
$(CLASSLIST_FILE_VM_OPTS) \
-cp $(SUPPORT_OUTPUTDIR)/classlist.jar \
build.tools.classlist.HelloClasslist $(LOG_DEBUG)
build.tools.classlist.HelloClasslist $(LOG_VERBOSE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed.

* Reenable validation
* Undoing debug stuff.
* Fix daft preview flag mistake.
* [[AUTOMATIC FORMATTING]]
* WIP - push for Roger.
* Builds to completion for "make images" but value tests don't run.
* Fix build issue with copied files.
* Automatic Java reformat
* Work in progress - builds but no tests yet.
* Tidying up inconsistencies.
* More encapsulation of JImage_file and mode into private static functions. Now the preview flag is plumbed into the JImage code, but not acted upon yet.
* Change method return back to base class type (nobody relies on the sub-type).
* Isolate JImage use within ClassPathImageEntry.
.map(path -> toPackageName(path))
.filter(pkg -> pkg.length() > 0)
.flatMap(Optional::stream)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mapMulti(Optional::ifPresent)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't actually work, since it ends up with a sequence of Object, not String.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if .<String>mapMulti(Optional::ifPresent)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean maybe (I'm sure it can be made to work), but is that any clearer at this stage?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mainly for performance - creating new streams have some overhead, because streams are stateful with closing, flags, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting. I had always assumed that Optional's stream() method would return something very cheap, since it has exactly one or zero elements. I'll change it then (and add a note). Thanks for educating me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants