Skip to content
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

8240567: MethodTooLargeException thrown while creating a jlink image #14408

Closed
wants to merge 32 commits into from

Conversation

koppor
Copy link
Contributor

@koppor koppor commented Jun 11, 2023

Fix for JDK-8240567: "MethodTooLargeException thrown while creating a jlink image".

Java still has a 64kb limit: A method may not be longer than 64kb. The idea of the fix is to split up the generated methods in several smaller methods

This is a follow-up to #10704. GitHub did not allow me to re-open the PR, because I did a force-push to have one commit.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8240567: MethodTooLargeException thrown while creating a jlink image (Bug - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 14408

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

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/14408.diff

Webrev

Link to Webrev Comment

@koppor koppor mentioned this pull request Jun 11, 2023
@bridgekeeper
Copy link

bridgekeeper bot commented Jun 11, 2023

👋 Welcome back koppor! A progress list of the required criteria for merging this PR into master 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 Jun 11, 2023

@koppor The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Jun 11, 2023
@koppor koppor changed the title Fixes JDK-8240567 by splitting up genModuleDescriptorsMethod 8240567: MethodTooLargeException thrown while creating a jlink image Jun 11, 2023
Co-authored-by: Christoph Schwentker <siedlerkiller@gmail.com>
@Siedlerchr
Copy link
Contributor

We validated this by compiling JabRef against it and a) writing out the generated class file for the generated bytecode and b) verifying with -Xlog:init=debug -XX:+UnlockDiagnosticVMOptions -XX:+BytecodeVerificationLocal

Is there a way to "parse" or call the verifyer after generation?

@koppor koppor marked this pull request as ready for review June 11, 2023 23:12
@openjdk openjdk bot added the rfr Pull request is ready for review label Jun 11, 2023
@mlbridge
Copy link

mlbridge bot commented Jun 11, 2023

Copy link
Contributor

@AlanBateman AlanBateman left a comment

Choose a reason for hiding this comment

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

@koppor Is this ready for review? The other PR went through a dozens or so iterations before it was returned to draft. It seems like you were still battling with verifier errors. The comment on this PR says you it was created because a force-push so I can't tell if you the changes are ready or not.


// Restore all (!) sets from parameter to local variables
if (nextLocalVar > firstVariableForDedup) {
// We need to go from the end to the beginning as we will probably overwrite position 2 (which holds the list at the beginning)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mind fixing all the comments as these 160+ lines make it impossible to look at the changes side-by-side again. You had fixed that in the original PR but it looks like they have come back with this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mind fixing all the comments as these 160+ lines

Done. I copied the (for me) important comments to the GitHub reply #14408 (comment).

You had fixed that in the original PR but it looks like they have come back with this PR.

This is my job training 😇: Comment the core ideas (and do not verbatim repeat what is said by the code directly). - For me, the good thing is, the diff now has all the comments (23bbc0c). OK, at least one has go to nirvana in all cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, the verify errors are gone. Application (JabRef) starts sucessfully against the compilation. Is there a way to verify in a tesr the creation of the helper methos in the bytecode? e.g. something like getDeclaredMethods?

@openjdk
Copy link

openjdk bot commented Jun 12, 2023

@koppor Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information.

@koppor
Copy link
Contributor Author

koppor commented Jun 12, 2023

TL;DR: summary of the changes

  • In the other PR we missed that dedupSetBuilder requires local variables in the method for caching
    • Code comments: // Restore all (!) sets from parameter to local variables and // Store all new sets to List
  • In this PR, these variables are transferred from method to method by a ArrayList
  • 0 is this when a method is called. this is needed to call the next helper method, thus we moved BUILDER_VAR to another place.
    • Code comments: MAX_LOCAL_VARS + 1; // we need 0 for "this" and on MAX_LOCAL_VARS: // Constant chosen for this generator, can be higher in practice

@koppor Is this ready for review?

Yes, it is! We especially checked it with our (rather large) desktop application.

The other PR went through a dozens or so iterations before it was returned to draft.

I remember 😅. Therefore, we first had this as draft, waited for the "on-site checks" and then opened it.

It seems like you were still battling with verifier errors.

We missed an important point and the other PR.

The comment on this PR says you it was created because a force-push so I can't tell if you the changes are ready or not.

I am sorry for that. I weighted the requirement to have a single commit higher than reviewing the changes. The "excuse" for this decision is that nearly all code submitted back than has changed.

@koppor
Copy link
Contributor Author

koppor commented Jun 14, 2023

@asotona At https://bugs.openjdk.org/browse/JDK-8240567?focusedCommentId=14588425&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-14588425 you asked the following:

Did you consider also constant pool 65k entries size limit?
When module descriptors are split into multiple methods due to their code size, the system modules plugin may soon hit the class constant pool limit of 65k unique entries

I don't have a bugs.openjdk.org account; thus I reply here.

The constant pool is also an "interesting" limitation. We did not address it (yet) because the current patch is IMHO a major step forward: It unblocks feature development of our software: We can add new external libraries 🎉

Looking more close to that limit and the implications to the patch: I understand that this limit affects the number of methods in a class. If this is correct, let's assume that a project requires thousands of modules and would use half of the limit for the generation of the module descriptors. That would result in roughly 32,000 methods. The patch puts 50 module descriptors in one method. Assuming 32,000 methods leads to ~1.6 million modules for a Java program.

Currently, our project JabRef has a total number of ~130 modules. We cannot move forward with our release as we cannot add new dependencies. We hope that other projects with many modules will benefit from this patch as well.

@koppor
Copy link
Contributor Author

koppor commented Jun 16, 2023

@AlanBateman The diff to the "old" PR #10704 is as follows. Maybe this helps at reviewing? IMHO this diff shows very good the new passing of the locals required by the deduplication functionality.

diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java
old mode 100755
new mode 100644
index 8e98a5b94e2..a87fa84ebe2
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java
@@ -118,7 +118,7 @@ public final class SystemModulesPlugin extends AbstractPlugin {
             ClassDesc.ofInternalName("jdk/internal/module/SystemModules");
     private static final ClassDesc CD_SYSTEM_MODULES_MAP =
             ClassDesc.ofInternalName(SYSTEM_MODULES_MAP_CLASSNAME);
-    private final boolean enabled;
+    private boolean enabled;
 
     public SystemModulesPlugin() {
         super("system-modules");
@@ -519,7 +519,7 @@ public final class SystemModulesPlugin extends AbstractPlugin {
 
         private static final int MAX_LOCAL_VARS = 256;
 
-        private final int BUILDER_VAR    = 0;
+        private final int BUILDER_VAR    = MAX_LOCAL_VARS + 1;
         private final int MD_VAR         = 1;  // variable for ModuleDescriptor
         private final int MT_VAR         = 1;  // variable for ModuleTarget
         private final int MH_VAR         = 1;  // variable for ModuleHashes
@@ -666,7 +666,7 @@ public final class SystemModulesPlugin extends AbstractPlugin {
          * Generate bytecode for moduleDescriptors method
          */
         private void genModuleDescriptorsMethod(ClassBuilder clb) {
-            if (moduleInfos.size() <= 71) {
+            if (moduleInfos.size() <= 75) {
                 clb.withMethodBody(
                         "moduleDescriptors",
                         MethodTypeDesc.of(CD_MODULE_DESCRIPTOR.arrayType()),
@@ -688,71 +688,98 @@ public final class SystemModulesPlugin extends AbstractPlugin {
                 return;
             }
 
-            // split up module infos in "consumable" packages
             List<List<ModuleInfo>> splitModuleInfos = new ArrayList<>();
             List<ModuleInfo> currentModuleInfos = null;
             for (int index = 0; index < moduleInfos.size(); index++) {
-                // The method is "manually split" based on the heuristics that 90 ModuleDescriptors are smaller than 64kb
-                // The number 10 is chosen "randomly" to be below the 64kb limit of a method
-                if (index % 10 == 0) {
-                    // Prepare new list
+                if (index % 50 == 0) {
                     currentModuleInfos = new ArrayList<>();
                     splitModuleInfos.add(currentModuleInfos);
                 }
                 currentModuleInfos.add(moduleInfos.get(index));
             }
-            // generate all helper methods
+
             final String helperMethodNamePrefix = "moduleDescriptorsSub";
+            final ClassDesc arrayListClassDesc = ClassDesc.ofInternalName("java/util/ArrayList");
+
+            final int firstVariableForDedup = nextLocalVar;
+
+            clb.withMethodBody(
+                    "moduleDescriptors",
+                    MethodTypeDesc.of(CD_MODULE_DESCRIPTOR.arrayType()),
+                    ACC_PUBLIC,
+                    cob -> {
+                        cob.constantInstruction(moduleInfos.size())
+                                .anewarray(CD_MODULE_DESCRIPTOR)
+                                .dup()
+                                .astore(MD_VAR);
+                        cob.new_(arrayListClassDesc)
+                           .dup()
+                           .invokespecial(arrayListClassDesc, "<init>", MethodTypeDesc.of(CD_void))
+                           .astore(nextLocalVar);
+                        cob.aload(0)
+                           .aload(MD_VAR)
+                           .aload(nextLocalVar)
+                           .invokevirtual(
+                                   this.classDesc,
+                                   helperMethodNamePrefix + "0",
+                                   MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc)
+                           )
+                           .areturn();
+                    });
+
             final int[] globalCount = {0};
             for (final int[] index = {0}; index[0] < splitModuleInfos.size(); index[0]++) {
                 clb.withMethodBody(
                         helperMethodNamePrefix + index[0],
-                        MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType()),
+                        MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc),
                         ACC_PUBLIC,
                         cob -> {
                             List<ModuleInfo> moduleInfosPackage = splitModuleInfos.get(index[0]);
-                            if (index[0] > 0) {
-                                // call last helper method
-                                cob.aload(0)
-                                   .aload(MD_VAR) // load first parameter, which is MD_VAR
-                                   .invokevirtual(
-                                           this.classDesc,
-                                           helperMethodNamePrefix + (index[0] - 1),
-                                           MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType())
-                                   );
+
+                            if (nextLocalVar > firstVariableForDedup) {
+                                for (int i = nextLocalVar-1; i >= firstVariableForDedup; i--) {
+                                    cob.aload(2)
+                                       .constantInstruction(i-firstVariableForDedup)
+                                       .invokevirtual(arrayListClassDesc, "get", MethodTypeDesc.of(CD_Object, CD_int))
+                                       .astore(i);
+                                }
                             }
+
                             for (int j = 0; j < moduleInfosPackage.size(); j++) {
                                 ModuleInfo minfo = moduleInfosPackage.get(j);
-                                // executed after the call, thus it is OK to overwrite index 0 (BUILDER_VAR)
                                 new ModuleDescriptorBuilder(cob,
                                         minfo.descriptor(),
                                         minfo.packages(),
                                         globalCount[0]).build();
                                 globalCount[0]++;
                             }
+
+                            if (index[0] + 1 < (splitModuleInfos.size())) {
+                                if (nextLocalVar > firstVariableForDedup) {
+                                    cob.new_(arrayListClassDesc)
+                                       .dup()
+                                       .invokespecial(arrayListClassDesc, "<init>", MethodTypeDesc.of(CD_void))
+                                       .astore(nextLocalVar);
+                                    for (int i = firstVariableForDedup; i < nextLocalVar; i++) {
+                                        cob.aload(nextLocalVar)
+                                           .aload(i)
+                                           .invokevirtual(arrayListClassDesc, "add", MethodTypeDesc.of(CD_boolean, CD_Object))
+                                           .pop();
+                                    }
+                                }
+                                cob.aload(0)
+                                   .aload(MD_VAR)
+                                   .aload(nextLocalVar)
+                                   .invokevirtual(
+                                           this.classDesc,
+                                           helperMethodNamePrefix + (index[0] + 1),
+                                           MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc)
+                                   );
+                            }
+
                             cob.return_();
                         });
             }
-
-            // generate call to last helper method
-            clb.withMethodBody(
-                    "moduleDescriptors",
-                    MethodTypeDesc.of(CD_MODULE_DESCRIPTOR.arrayType()),
-                    ACC_PUBLIC,
-                    cob -> {
-                        cob.constantInstruction(moduleInfos.size())
-                           .anewarray(CD_MODULE_DESCRIPTOR)
-                           .astore(MD_VAR)
-                           .aload(MD_VAR) // storing for the return at the end
-                           .aload(0)
-                           .aload(MD_VAR)
-                           .invokevirtual(
-                                   this.classDesc,
-                                   helperMethodNamePrefix + (splitModuleInfos.size() - 1),
-                                   MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType())
-                           )
-                           .areturn();
-                    });
         }
 
         /**
diff --git a/test/jdk/tools/jlink/JLink100Modules.java b/test/jdk/tools/jlink/JLink100Modules.java
index 4a6111505d2..e71206c904f 100644
--- a/test/jdk/tools/jlink/JLink100Modules.java
+++ b/test/jdk/tools/jlink/JLink100Modules.java
@@ -27,8 +27,10 @@ import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.StringJoiner;
 import java.util.spi.ToolProvider;
+
 import tests.JImageGenerator;
 import tests.JImageGenerator.JLinkTask;
+
 /*
  * @test
  * @summary Make sure that 100 modules can be linked using jlink.
@@ -46,9 +48,9 @@ import tests.JImageGenerator.JLinkTask;
  */
 public class JLink100Modules {
     private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac")
-                                                               .orElseThrow(() -> new RuntimeException("javac tool not found"));
+            .orElseThrow(() -> new RuntimeException("javac tool not found"));
     private static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
-                                                               .orElseThrow(() -> new RuntimeException("jlink tool not found"));
+            .orElseThrow(() -> new RuntimeException("jlink tool not found"));
 
     static void report(String command, String[] args) {
         System.out.println(command + " " + String.join(" ", Arrays.asList(args)));
@@ -77,9 +79,10 @@ public class JLink100Modules {
             StringBuilder builder = new StringBuilder("module ");
             builder.append(name).append(" {");
 
-            for (int j = 0; j < i; j++) {
-                builder.append("requires module" + j + "x;");
+            if (i != 0) {
+                builder.append("requires module0x;");
             }
+
             builder.append("}\n");
             Files.writeString(moduleDir.resolve("module-info.java"), builder.toString());
             mainModuleInfoContent.add(name);
@@ -106,7 +109,7 @@ public class JLink100Modules {
 
         String out = src.resolve("out").toString();
 
-        javac(new String[] {
+        javac(new String[]{
                 "-d", out,
                 "--module-source-path", src.toString(),
                 "--module", "bug8240567x"
@@ -116,6 +119,7 @@ public class JLink100Modules {
                 .modulePath(out)
                 .output(src.resolve("out-jlink"))
                 .addMods("bug8240567x")
-                .call().assertSuccess();
+                .call()
+                .assertSuccess();
     }
 }

@AlanBateman
Copy link
Contributor

@AlanBateman The diff to the "old" PR #10704 is as follows. Maybe this helps at reviewing? IMHO this diff shows very good the new passing of the locals required by the deduplication functionality.

It's good that you've got to a patch that doesn't trip the verifier. It is one my list to look at the latest version.

List<List<ModuleInfo>> splitModuleInfos = new ArrayList<>();
List<ModuleInfo> currentModuleInfos = null;
for (int index = 0; index < moduleInfos.size(); index++) {
if (index % 50 == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The size of the generated moduleDescriptors is a function of the number of modules, the number of packages in the modules, number of exports, ... I think your approach is okay for now, meaning pick some threshold that you are confident will result in a method size < 64k. If the number of modules generate a chained methods to popular the array. It might be clearer to readers to use one threshold rather than two.

The other limit that we will need to think about is the constant pool. That may be future work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The size of the generated moduleDescriptors is a function of the number of modules, the number of packages in the modules, number of exports, ... I think your approach is okay for now, meaning pick some threshold that you are confident will result in a method size < 64k.

I am very confident with 50 as 1) we tried out with a real life application using a high variation of dependencies (covering Apache libraries, Google libraries, and Oracle's JavaFX) and 2) it is half of the value of the number of modules where it broke at our software.

It might be clearer to readers to use one threshold rather than two.

Above, the 75 was chosen, to avoid that anything in the JDK distribution is split (number was 73 to be precise, but I did want to have a "round" number).

To be consistent, I adapted the lower split to 75. I tried it out with our app, and the app still works.

The other limit that we will need to think about is the constant pool. That may be future work.

I am leaning towards "future work" here. Nevertheless, could you outline how that limit might be reached? It IMHO cannot be the numbmer of helper functions. The current "limit" leads to 32,000 (constant pool limit; leaving the other 32k to other things) x 75 (number of module descriptors covered) = 2.4 million module descriptors. In that case we might hit the call stack limit.

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

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

Thanks for fixing this. The approach looks okay.

* @bug 8240567
* @library ../lib
* @modules java.base/jdk.internal.jimage
* jdk.jdeps/com.sun.tools.classfile
Copy link
Member

Choose a reason for hiding this comment

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

I guess you copied this from other jlink tests. Do you know why jlink tests need com.sun.tools.classfile?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it is not present, I get following output (sorry for German - ./configure doesn't allow JAVA_TOOL_OPTIONS to be set to -Duser.language=en

windir='C:\WINDOWS' \
    'c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\images\jdk\bin\javac' \
        -J-Xmx768m \
        -J-XX:MaxRAMPercentage=2.08333 \
        -J'-Dtest.boot.jdk=c:\progra~1\openjdk\jdk-20~1.1' \
        -J'-Djava.io.tmpdir=c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\tmp' \
        -J-ea \
        -J-esa \
        -J-Dtest.vm.opts='-Xmx768m -XX:MaxRAMPercentage=2.08333 -Dtest.boot.jdk=c:\progra~1\openjdk\jdk-20~1.1 -Djava.io.tmpdir=c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\tmp -ea -esa' \
        -J-Dtest.tool.vm.opts='-J-Xmx768m -J-XX:MaxRAMPercentage=2.08333 -J-Dtest.boot.jdk=c:\progra~1\openjdk\jdk-20~1.1 -J-Djava.io.tmpdir=c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\tmp -J-ea -J-esa' \
        -J-Dtest.compiler.opts= \
        -J-Dtest.java.opts= \
        -J-Dtest.jdk='c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\images\jdk' \
        -J-Dcompile.jdk='c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\images\jdk' \
        -J-Dtest.timeout.factor=4.0 \
        -J-Dtest.nativepath='c:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\images\test\jdk\jtreg\native' \
        -J-Dtest.root='C:\git-repositories\jdk\jdk\test\jdk' \
        -J-Dtest.name=tools/jlink/JLink100Modules.java \
        -J-Dtest.file='C:\git-repositories\jdk\jdk\test\jdk\tools\jlink\JLink100Modules.java' \
        -J-Dtest.src='C:\git-repositories\jdk\jdk\test\jdk\tools\jlink' \
        -J-Dtest.src.path='C:\git-repositories\jdk\jdk\test\jdk\tools\jlink;C:\git-repositories\jdk\jdk\test\jdk\tools\lib' \
        -J-Dtest.classes='C:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\classes\0\tools\jlink\JLink100Modules.d' \
        -J-Dtest.class.path='C:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\classes\0\tools\jlink\JLink100Modules.d;C:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\classes\0\tools\lib' \
        -J-Dtest.class.path.prefix='C:\git-repositories\jdk\jdk\test\jdk\tools\jlink;C:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\classes\0\tools\lib' \
        -J-Dtest.modules='java.base/jdk.internal.jimage jdk.jlink/jdk.tools.jlink.internal jdk.jlink/jdk.tools.jlink.plugin jdk.jlink/jdk.tools.jmod jdk.jlink/jdk.tools.jimage jdk.compiler' \
        --add-modules java.base,jdk.jlink,jdk.compiler \
        --add-exports java.base/jdk.internal.jimage=ALL-UNNAMED \
        --add-exports jdk.jlink/jdk.tools.jlink.internal=ALL-UNNAMED \
        --add-exports jdk.jlink/jdk.tools.jlink.plugin=ALL-UNNAMED \
        --add-exports jdk.jlink/jdk.tools.jmod=ALL-UNNAMED \
        --add-exports jdk.jlink/jdk.tools.jimage=ALL-UNNAMED \
        -d 'C:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\classes\0\tools\lib' \
        -sourcepath 'C:\git-repositories\jdk\jdk\test\jdk\tools\lib' \
        -classpath 'C:\git-repositories\jdk\jdk\build\windows-x86_64-server-release\test-support\jtreg_test_jdk_tools_jlink_JLink100Modules_java\classes\0\tools\lib' 'C:\git-repositories\jdk\jdk\test\jdk\tools\lib\tests\Helper.java' 'C:\git-repositories\jdk\jdk\test\jdk\tools\lib\tests\JImageGenerator.java' 'C:\git-repositories\jdk\jdk\test\jdk\tools\lib\tests\JImageValidator.java' 'C:\git-repositories\jdk\jdk\test\jdk\tools\lib\tests\Result.java'
direct:
C:\git-repositories\jdk\jdk\test\jdk\tools\lib\tests\JImageValidator.java:36: Fehler: Package com.sun.tools.classfile ist nicht sichtbar
import com.sun.tools.classfile.ClassFile;
                    ^
  (Package com.sun.tools.classfile wird in Modul jdk.jdeps deklariert, aber nicht in das unbenannte Modul exportiert)
C:\git-repositories\jdk\jdk\test\jdk\tools\lib\tests\JImageValidator.java:37: Fehler: Package com.sun.tools.classfile ist nicht sichtbar
import com.sun.tools.classfile.ConstantPoolException;
                    ^
  (Package com.sun.tools.classfile wird in Modul jdk.jdeps deklariert, aber nicht in das unbenannte Modul exportiert)
2 Fehler

Copy link
Member

Choose a reason for hiding this comment

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

I see now. It's used by JImageValidator and that can be replaced by the ClassFile API in the future. Thanks.

* jdk.jlink/jdk.tools.jimage
* jdk.compiler
* @build tests.*
* @run main/othervm -verbose:gc -Xmx1g -Xlog:init=debug -XX:+UnlockDiagnosticVMOptions -XX:+BytecodeVerificationLocal JLink100Modules
Copy link
Member

Choose a reason for hiding this comment

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

This patch does not change the bytecode if it's less than 75 modules. You want to apply -XX:+UnlockDiagnosticVMOptions -XX:+BytecodeVerificationLocal flags also to a custom image with the new bytecodes. So this test should include an execution of JLink100ModulesTest from out-jlink image with these flags.

Copy link
Member

Choose a reason for hiding this comment

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

BTW, is -verbose:gc leftover from debugging?

@@ -666,25 +666,120 @@ private void genIncubatorModules(ClassBuilder clb) {
* Generate bytecode for moduleDescriptors method
*/
private void genModuleDescriptorsMethod(ClassBuilder clb) {
if (moduleInfos.size() <= 75) {
Copy link
Member

Choose a reason for hiding this comment

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

The plugin can take an optional argument to configure the max number of module infos to split in case it hits the limit in a future case.

Copy link
Member

Choose a reason for hiding this comment

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

What I meant here is to change the configure method to allow the plugin to take an argument for example

    --system-modules batch-size=100

This argument is optional. If not specified, the default value is 75. This way you can tune the batch-size if we hit the method size limit for whatever reason. And the test can also verify with different size if appropriate.

Copy link
Member

Choose a reason for hiding this comment

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

plugins.properties needs to be updated to show the option added for this plugin.

BTW, the current argument described in the usage is out-dated which needs update.

system-modules.argument=retainModuleTarget

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added it at 9835a64. This is not for the main ./configure. To which configure does that go?

Copy link
Member

Choose a reason for hiding this comment

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

SystemModulesPlugin::configure

return;
}

List<List<ModuleInfo>> splitModuleInfos = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

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

Please add the comments to describe what this does. Pseudo code may also help. It'd be helpful to explain the reason why the Sub method stores and restores what local variables.

I also wonder if you consider moduleDescriptors does this:

    ArrayList<Object> localVars = new ArrayList<>();
    moduleDescriptorsSub0(moduleInfos, localVars);
    .... // add new local variables to localVars
    moduleDescriptorsSub1(moduleInfos, localVars);
    .... // add new local variables to localVars
    :
    :

The same localVars array list is used and just add the new local variables defined from each batch (no need to create a new ArrayList and stores local variables every time)

Copy link
Contributor Author

@koppor koppor Jul 1, 2023

Choose a reason for hiding this comment

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

I assume the "old" comments were too detailed. I removed them at JabRef/jdk@23bbc0c (#2) to have the code reviewable. I can readd some of them if that helps.

I also added a compressed description of the idea at edd85c9 (#14408). Update here, in the PR one simply needs to scroll up and sees the new comment.

Copy link
Member

Choose a reason for hiding this comment

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

Have you considered passing the same ArrayList for saving and restoring local variables? Currently each method creates one new array list and save the local variables from firstVariableForDedup to nextLocalVar, but the local variables from firstVariableForDedup are already added to the array list created by the previous method.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I see your commit. I will check it out.

@koppor
Copy link
Contributor Author

koppor commented Jul 4, 2023

Thanks for the update. Some comments below.

Thank you for the feedback! I didn't see the "proper" usage of effectively final variables.

The test you add does not cause new locals be defined in the helper methods. Do you think you can add such test case i.e. new elements are added in the dedup var list by the helper functions?

I modified the "end-to-end" test in 15d7448 (#14408).

Note that the numbers cannot be arbitrarily increased. Otherwise, one gets errors similar to following

Fehler: java.lang.IllegalArgumentException: Code length 102397 is outside the allowed range in sub1(ModuleDescriptor[],ArrayList)void

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

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

It's looking pretty good.

About the test, I don't see ArrayList::add in the generated bytecode of sub2-13. The dedup string set is used for the targets of qualified exports and opens and uses. The modifiers set of requires is deduplicated but not the required module names.

I assume you want this be backported. If so, we should give it some baked time after it's integrated to the main line. I'm okay if you want to extend the test in a follow up. You can extract the generated class to verify:

$ jimage extract --dir=dir out-jlink/lib/modules
$ javap -p -v dir/java.base/jdk/internal/module/SystemModules\$all.class

Path binDir = src.resolve("out-jlink").resolve("bin").toAbsolutePath();
Path bin = binDir.resolve("java");

ProcessBuilder processBuilder = new ProcessBuilder(bin.toString(), "-XX:+UnlockDiagnosticVMOptions", "-XX:+BytecodeVerificationLocal", "-m", "bug8240567x/testpackage.JLink100ModulesTest");
Copy link
Member

Choose a reason for hiding this comment

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

please wrap this long line into multiple lines.

test/jdk/tools/jlink/JLink100Modules.java Outdated Show resolved Hide resolved
@koppor
Copy link
Contributor Author

koppor commented Jul 5, 2023

It's looking pretty good.

Thank you!

About the test, I don't see ArrayList::add in the generated bytecode of sub2-13. The dedup string set is used for the targets of qualified exports and opens and uses. The modifiers set of requires is deduplicated but not the required module names.

Thank you for the hint.

I assume you want this be backported. If so, we should give it some baked time after it's integrated to the main line.

Sounds great!

I'm okay if you want to extend the test in a follow up.

That would be great. Will take time to craft a proper setting.

Comment on lines 72 to 75
// feed SystemModulesPlugin.SystemModulesClassGenerator.DedupSetBuilder
for (int j = 0; j < i % 20; j++) {
moduleInfoContent.append(" requires module" + j + "x;\n");
}
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to keep this change? or at least take out the comment since this doesn't feed DedupSetBuilder.

Can you add a comment about "the numbers cannot be arbitrarily increased".

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 reverted at 8494517 (#14408). Think, the comment is then not necessary?

Copy link
Member

@mlchung mlchung left a comment

Choose a reason for hiding this comment

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

I approve this patch. I'll create a JBS issue to follow up the test update.

@openjdk
Copy link

openjdk bot commented Jul 5, 2023

@koppor This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8240567: MethodTooLargeException thrown while creating a jlink image

Reviewed-by: mchung

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been no new commits pushed to the master branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@AlanBateman, @mlchung) but any other Committer may sponsor as well.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jul 5, 2023
@koppor
Copy link
Contributor Author

koppor commented Jul 6, 2023

/integrate

@openjdk openjdk bot added the sponsor Pull request is ready to be sponsored label Jul 6, 2023
@openjdk
Copy link

openjdk bot commented Jul 6, 2023

@koppor
Your change (at version 584edba) is now ready to be sponsored by a Committer.

@mlchung
Copy link
Member

mlchung commented Jul 6, 2023

I created https://bugs.openjdk.org/browse/JDK-8311591. This issue should not be backport until JDK-8311591 is resolved.

@mlchung
Copy link
Member

mlchung commented Jul 6, 2023

/sponsor

@openjdk
Copy link

openjdk bot commented Jul 6, 2023

Going to push as commit ec7da91.
Since your change was applied there have been 6 commits pushed to the master branch:

  • 97e99f0: 8311087: PhiNode::wait_for_region_igvn should break early
  • 7173c30: 8307766: Linux: Provide the option to override the timer slack
  • 356067d: 8311489: Remove unused dirent_md files
  • 3d813ae: 8311301: MethodExitTest may fail with stack buffer overrun
  • 0741cd3: 8311264: JavaDoc index comparator is not transitive
  • edb2be1: 8311279: TestStressIGVNAndCCP.java failed with different IGVN traces for the same seed

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jul 6, 2023
@openjdk openjdk bot closed this Jul 6, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review sponsor Pull request is ready to be sponsored labels Jul 6, 2023
@openjdk
Copy link

openjdk bot commented Jul 6, 2023

@mlchung @koppor Pushed as commit ec7da91.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@mlchung
Copy link
Member

mlchung commented Jul 14, 2023

@koppor do you have any update on a new test for JDK-8311591?

@koppor
Copy link
Contributor Author

koppor commented Jul 15, 2023

@koppor do you have any update on a new test for JDK-8311591?

Currently on a business trip. Will resume work on this next week the latest.

@Siedlerchr
Copy link
Contributor

@mlchung Pardon for the delay, we now created a PR with a test case #15234

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

4 participants