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 index c01042bdc94e7..c04ff2b02afa9 100644 --- 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 boolean enabled; + private final boolean enabled; public SystemModulesPlugin() { super("system-modules"); @@ -517,9 +517,10 @@ static class SystemModulesClassGenerator { private static final ClassDesc CD_MODULE_RESOLUTION = ClassDesc.ofInternalName("jdk/internal/module/ModuleResolution"); + // Constant chosen for this generator, can be higher in practice private static final int MAX_LOCAL_VARS = 256; - private final int BUILDER_VAR = 0; + private final int BUILDER_VAR = MAX_LOCAL_VARS + 1; // we need 0 for "this" 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,25 +667,140 @@ private void genIncubatorModules(ClassBuilder clb) { * Generate bytecode for moduleDescriptors method */ private void genModuleDescriptorsMethod(ClassBuilder clb) { + if (moduleInfos.size() <= 75) { + // In case there won't be a Method_Too_Large exception, we use the unsplit method to generate the method "moduleDescriptors" + clb.withMethodBody( + "moduleDescriptors", + MethodTypeDesc.of(CD_MODULE_DESCRIPTOR.arrayType()), + ACC_PUBLIC, + cob -> { + cob.constantInstruction(moduleInfos.size()) + .anewarray(CD_MODULE_DESCRIPTOR) + .astore(MD_VAR); + for (int index = 0; index < moduleInfos.size(); index++) { + ModuleInfo minfo = moduleInfos.get(index); + new ModuleDescriptorBuilder(cob, + minfo.descriptor(), + minfo.packages(), + index).build(); + } + cob.aload(MD_VAR) + .areturn(); + }); + return; + } + + // split up module infos in "consumable" packages + List> splitModuleInfos = new ArrayList<>(); + List 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 50 is chosen "randomly" to be below the 64kb limit of a method + if (index % 50 == 0) { + // Prepare new list + currentModuleInfos = new ArrayList<>(); + splitModuleInfos.add(currentModuleInfos); + } + currentModuleInfos.add(moduleInfos.get(index)); + } + + final String helperMethodNamePrefix = "moduleDescriptorsSub"; + // Variable holding List, which needs to be restored at each helper method + // This list grows at each call of each helper method + final ClassDesc arrayListClassDesc = ClassDesc.ofInternalName("java/util/ArrayList"); + + // dedupSetBuilder will (!) use this index for the first variable + final int firstVariableForDedup = nextLocalVar; + + // generate call to first 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); - - for (int index = 0; index < moduleInfos.size(); index++) { - ModuleInfo minfo = moduleInfos.get(index); - new ModuleDescriptorBuilder(cob, - minfo.descriptor(), - minfo.packages(), - index).build(); - } - cob.aload(MD_VAR) + .anewarray(CD_MODULE_DESCRIPTOR) + .dup() // storing for the return at the end + .astore(MD_VAR); + // Generate List of Sets required by dedupSetBuilder + // We use slot "nextLocalVar" temporarily. We do net need the list later as the helper methods modify the list and pass it on. + cob.new_(arrayListClassDesc) + .dup() + .invokespecial(arrayListClassDesc, "", 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(); }); + + // generate all helper methods + 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(), arrayListClassDesc), + ACC_PUBLIC, + cob -> { + List moduleInfosPackage = splitModuleInfos.get(index[0]); + + // 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) + 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())) { + // We are not the last one of the calling chain of helper methods + // Prepare next call + + // Store all new sets to List + if (nextLocalVar > firstVariableForDedup) { + cob.new_(arrayListClassDesc) + .dup() + .invokespecial(arrayListClassDesc, "", 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(); // remove boolean result value + } + } + // call to next helper method + cob.aload(0) + .aload(MD_VAR) // load first parameter, which is MD_VAR + .aload(nextLocalVar) + .invokevirtual( + this.classDesc, + helperMethodNamePrefix + (index[0] + 1), + MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc) + ); + } + + cob.return_(); + }); + } } /** diff --git a/test/jdk/tools/jlink/JLink100Modules.java b/test/jdk/tools/jlink/JLink100Modules.java new file mode 100644 index 0000000000000..e71206c904f4d --- /dev/null +++ b/test/jdk/tools/jlink/JLink100Modules.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.nio.file.Files; +import java.nio.file.Path; +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. + * @bug 8240567 + * @library ../lib + * @modules java.base/jdk.internal.jimage + * jdk.jdeps/com.sun.tools.classfile + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * jdk.jlink/jdk.tools.jmod + * jdk.jlink/jdk.tools.jimage + * jdk.compiler + * @build tests.* + * @run main/othervm -verbose:gc -Xmx1g -Xlog:init=debug -XX:+UnlockDiagnosticVMOptions -XX:+BytecodeVerificationLocal JLink100Modules + */ +public class JLink100Modules { + private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac") + .orElseThrow(() -> new RuntimeException("javac tool not found")); + private static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") + .orElseThrow(() -> new RuntimeException("jlink tool not found")); + + static void report(String command, String[] args) { + System.out.println(command + " " + String.join(" ", Arrays.asList(args))); + } + + static void javac(String[] args) { + report("javac", args); + JAVAC_TOOL.run(System.out, System.err, args); + } + + static void jlink(String[] args) { + report("jlink", args); + JLINK_TOOL.run(System.out, System.err, args); + } + + public static void main(String[] args) throws Exception { + Path src = Paths.get("bug8240567"); + + StringJoiner mainModuleInfoContent = new StringJoiner(";\n requires ", "module bug8240567x {\n requires ", "\n;}"); + + // create 100 modules. With this naming schema up to 130 seem to work + for (int i = 0; i < 150; i++) { + String name = "module" + i + "x"; + Path moduleDir = Files.createDirectories(src.resolve(name)); + + StringBuilder builder = new StringBuilder("module "); + builder.append(name).append(" {"); + + if (i != 0) { + builder.append("requires module0x;"); + } + + builder.append("}\n"); + Files.writeString(moduleDir.resolve("module-info.java"), builder.toString()); + mainModuleInfoContent.add(name); + } + + // create module reading the generated modules + Path mainModulePath = src.resolve("bug8240567x"); + Files.createDirectories(mainModulePath); + Path mainModuleInfo = mainModulePath.resolve("module-info.java"); + Files.writeString(mainModuleInfo, mainModuleInfoContent.toString()); + + Path mainClassDir = mainModulePath.resolve("testpackage"); + Files.createDirectories(mainClassDir); + + Files.writeString(mainClassDir.resolve("JLink100ModulesTest.java"), """ + package testpackage; + + public class JLink100ModulesTest { + public static void main(String[] args) throws Exception { + System.out.println("JLink100ModulesTest started."); + } + } + """); + + String out = src.resolve("out").toString(); + + javac(new String[]{ + "-d", out, + "--module-source-path", src.toString(), + "--module", "bug8240567x" + }); + + JImageGenerator.getJLinkTask() + .modulePath(out) + .output(src.resolve("out-jlink")) + .addMods("bug8240567x") + .call() + .assertSuccess(); + } +}