Skip to content

Commit ec7da91

Browse files
kopporMandy Chung
authored and
Mandy Chung
committedJul 6, 2023
8240567: MethodTooLargeException thrown while creating a jlink image
Reviewed-by: mchung
1 parent 97e99f0 commit ec7da91

File tree

3 files changed

+285
-19
lines changed

3 files changed

+285
-19
lines changed
 

‎src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java

+149-14
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ public final class SystemModulesPlugin extends AbstractPlugin {
120120
ClassDesc.ofInternalName(SYSTEM_MODULES_MAP_CLASSNAME);
121121
private static final MethodTypeDesc MTD_StringArray = MethodTypeDesc.of(CD_String.arrayType());
122122
private static final MethodTypeDesc MTD_SystemModules = MethodTypeDesc.of(CD_SYSTEM_MODULES);
123+
124+
private int moduleDescriptorsPerMethod = 75;
123125
private boolean enabled;
124126

125127
public SystemModulesPlugin() {
@@ -142,7 +144,14 @@ public boolean hasArguments() {
142144
public void configure(Map<String, String> config) {
143145
String arg = config.get(getName());
144146
if (arg != null) {
145-
throw new IllegalArgumentException(getName() + ": " + arg);
147+
String[] split = arg.split("=");
148+
if (split.length != 2) {
149+
throw new IllegalArgumentException(getName() + ": " + arg);
150+
}
151+
if (split[0].equals("batch-size")) {
152+
throw new IllegalArgumentException(getName() + ": " + arg);
153+
}
154+
this.moduleDescriptorsPerMethod = Integer.parseInt(split[1]);
146155
}
147156
}
148157

@@ -318,7 +327,7 @@ private String genSystemModulesClass(List<ModuleInfo> moduleInfos,
318327
String className,
319328
ResourcePoolBuilder out) {
320329
SystemModulesClassGenerator generator
321-
= new SystemModulesClassGenerator(className, moduleInfos);
330+
= new SystemModulesClassGenerator(className, moduleInfos, moduleDescriptorsPerMethod);
322331
byte[] bytes = generator.genClassBytes(cf);
323332
String rn = "/java.base/" + className + ".class";
324333
ResourcePoolEntry e = ResourcePoolEntry.create(rn, bytes);
@@ -533,28 +542,33 @@ static class SystemModulesClassGenerator {
533542

534543
private static final int MAX_LOCAL_VARS = 256;
535544

536-
private final int BUILDER_VAR = 0;
537545
private final int MD_VAR = 1; // variable for ModuleDescriptor
538546
private final int MT_VAR = 1; // variable for ModuleTarget
539547
private final int MH_VAR = 1; // variable for ModuleHashes
540-
private int nextLocalVar = 2; // index to next local variable
548+
private final int DEDUP_LIST_VAR = 2;
549+
private final int BUILDER_VAR = 3;
550+
private int nextLocalVar = 4; // index to next local variable
541551

542552
// name of class to generate
543553
private final ClassDesc classDesc;
544554

545555
// list of all ModuleInfos
546556
private final List<ModuleInfo> moduleInfos;
547557

558+
private final int moduleDescriptorsPerMethod;
559+
548560
// A builder to create one single Set instance for a given set of
549561
// names or modifiers to reduce the footprint
550562
// e.g. target modules of qualified exports
551563
private final DedupSetBuilder dedupSetBuilder
552564
= new DedupSetBuilder(this::getNextLocalVar);
553565

554566
public SystemModulesClassGenerator(String className,
555-
List<ModuleInfo> moduleInfos) {
567+
List<ModuleInfo> moduleInfos,
568+
int moduleDescriptorsPerMethod) {
556569
this.classDesc = ClassDesc.ofInternalName(className);
557570
this.moduleInfos = moduleInfos;
571+
this.moduleDescriptorsPerMethod = moduleDescriptorsPerMethod;
558572
moduleInfos.forEach(mi -> dedups(mi.descriptor()));
559573
}
560574

@@ -680,25 +694,146 @@ private void genIncubatorModules(ClassBuilder clb) {
680694
* Generate bytecode for moduleDescriptors method
681695
*/
682696
private void genModuleDescriptorsMethod(ClassBuilder clb) {
697+
if (moduleInfos.size() <= moduleDescriptorsPerMethod) {
698+
clb.withMethodBody(
699+
"moduleDescriptors",
700+
MTD_ModuleDescriptorArray,
701+
ACC_PUBLIC,
702+
cob -> {
703+
cob.constantInstruction(moduleInfos.size())
704+
.anewarray(CD_MODULE_DESCRIPTOR)
705+
.astore(MD_VAR);
706+
707+
for (int index = 0; index < moduleInfos.size(); index++) {
708+
ModuleInfo minfo = moduleInfos.get(index);
709+
new ModuleDescriptorBuilder(cob,
710+
minfo.descriptor(),
711+
minfo.packages(),
712+
index).build();
713+
}
714+
cob.aload(MD_VAR)
715+
.areturn();
716+
});
717+
return;
718+
}
719+
720+
721+
// Split the module descriptors be created by multiple helper methods.
722+
// Each helper method "subi" creates the maximum N number of module descriptors
723+
// mi, m{i+1} ...
724+
// to avoid exceeding the 64kb limit of method length. Then it will call
725+
// "sub{i+1}" to creates the next batch of module descriptors m{i+n}, m{i+n+1}...
726+
// and so on. During the construction of the module descriptors, the string sets and
727+
// modifier sets are deduplicated (see SystemModulesClassGenerator.DedupSetBuilder)
728+
// and cached in the locals. These locals are saved in an array list so
729+
// that the helper method can restore the local variables that may be
730+
// referenced by the bytecode generated for creating module descriptors.
731+
// Pseudo code looks like this:
732+
//
733+
// void subi(ModuleDescriptor[] mdescs, ArrayList<Object> localvars) {
734+
// // assign localvars to local variables
735+
// var l3 = localvars.get(0);
736+
// var l4 = localvars.get(1);
737+
// :
738+
// // fill mdescs[i] to mdescs[i+n-1]
739+
// mdescs[i] = ...
740+
// mdescs[i+1] = ...
741+
// :
742+
// // save new local variables added
743+
// localvars.add(lx)
744+
// localvars.add(l{x+1})
745+
// :
746+
// sub{i+i}(mdescs, localvars);
747+
// }
748+
749+
List<List<ModuleInfo>> splitModuleInfos = new ArrayList<>();
750+
List<ModuleInfo> currentModuleInfos = null;
751+
for (int index = 0; index < moduleInfos.size(); index++) {
752+
if (index % moduleDescriptorsPerMethod == 0) {
753+
currentModuleInfos = new ArrayList<>();
754+
splitModuleInfos.add(currentModuleInfos);
755+
}
756+
currentModuleInfos.add(moduleInfos.get(index));
757+
}
758+
759+
String helperMethodNamePrefix = "sub";
760+
ClassDesc arrayListClassDesc = ClassDesc.ofInternalName("java/util/ArrayList");
761+
683762
clb.withMethodBody(
684763
"moduleDescriptors",
685764
MTD_ModuleDescriptorArray,
686765
ACC_PUBLIC,
687766
cob -> {
688767
cob.constantInstruction(moduleInfos.size())
689768
.anewarray(CD_MODULE_DESCRIPTOR)
769+
.dup()
690770
.astore(MD_VAR);
691-
692-
for (int index = 0; index < moduleInfos.size(); index++) {
693-
ModuleInfo minfo = moduleInfos.get(index);
694-
new ModuleDescriptorBuilder(cob,
695-
minfo.descriptor(),
696-
minfo.packages(),
697-
index).build();
698-
}
699-
cob.aload(MD_VAR)
771+
cob.new_(arrayListClassDesc)
772+
.dup()
773+
.constantInstruction(moduleInfos.size())
774+
.invokespecial(arrayListClassDesc, INIT_NAME, MethodTypeDesc.of(CD_void, CD_int))
775+
.astore(DEDUP_LIST_VAR);
776+
cob.aload(0)
777+
.aload(MD_VAR)
778+
.aload(DEDUP_LIST_VAR)
779+
.invokevirtual(
780+
this.classDesc,
781+
helperMethodNamePrefix + "0",
782+
MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc)
783+
)
700784
.areturn();
701785
});
786+
787+
int dedupVarStart = nextLocalVar;
788+
for (int n = 0, count = 0; n < splitModuleInfos.size(); count += splitModuleInfos.get(n).size(), n++) {
789+
int index = n; // the index of which ModuleInfo being processed in the current batch
790+
int start = count; // the start index to the return ModuleDescriptor array for the current batch
791+
int curDedupVar = nextLocalVar;
792+
clb.withMethodBody(
793+
helperMethodNamePrefix + index,
794+
MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc),
795+
ACC_PUBLIC,
796+
cob -> {
797+
if (curDedupVar > dedupVarStart) {
798+
for (int i = dedupVarStart; i < curDedupVar; i++) {
799+
cob.aload(DEDUP_LIST_VAR)
800+
.constantInstruction(i - dedupVarStart)
801+
.invokevirtual(arrayListClassDesc, "get", MethodTypeDesc.of(CD_Object, CD_int))
802+
.astore(i);
803+
}
804+
}
805+
806+
List<ModuleInfo> currentBatch = splitModuleInfos.get(index);
807+
for (int j = 0; j < currentBatch.size(); j++) {
808+
ModuleInfo minfo = currentBatch.get(j);
809+
new ModuleDescriptorBuilder(cob,
810+
minfo.descriptor(),
811+
minfo.packages(),
812+
start + j).build();
813+
}
814+
815+
if (index < splitModuleInfos.size() - 1) {
816+
if (nextLocalVar > curDedupVar) {
817+
for (int i = curDedupVar; i < nextLocalVar; i++) {
818+
cob.aload(DEDUP_LIST_VAR)
819+
.aload(i)
820+
.invokevirtual(arrayListClassDesc, "add", MethodTypeDesc.of(CD_boolean, CD_Object))
821+
.pop();
822+
}
823+
}
824+
cob.aload(0)
825+
.aload(MD_VAR)
826+
.aload(DEDUP_LIST_VAR)
827+
.invokevirtual(
828+
this.classDesc,
829+
helperMethodNamePrefix + (index+1),
830+
MethodTypeDesc.of(CD_void, CD_MODULE_DESCRIPTOR.arrayType(), arrayListClassDesc)
831+
);
832+
}
833+
834+
cob.return_();
835+
});
836+
}
702837
}
703838

704839
/**

‎src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties

+8-5
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,16 @@ generate-jli-classes.usage=\
147147
\ correctness add ignore-version=true\n\
148148
\ to override this.
149149

150-
system-modules.argument=retainModuleTarget
151-
152-
system-modules.description=Fast loading of module descriptors (always enabled)
150+
system-modules.argument=batch-size=<N> sets the batch size of module descriptors\n\
151+
\ to avoid exceeding the method length limit. The default\n\
152+
\ batch size is 75.
153153

154154
system-modules.usage=\
155-
\ --system-modules retainModuleTarget\n\
156-
\ Fast loading of module descriptors (always enabled)
155+
\ --system-modules [batch-size=<N>]\n\
156+
\ The batch size specifies the maximum number of modules\n\
157+
\ be handled in one method to workaround if the generated\n\
158+
\ bytecode exceeds the method size limit. The default\n\
159+
\ batch size is 75.
157160

158161
onoff.argument=<on|off>
159162

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
27+
import java.util.Arrays;
28+
import java.util.StringJoiner;
29+
import java.util.spi.ToolProvider;
30+
31+
import tests.JImageGenerator;
32+
33+
/*
34+
* @test
35+
* @summary Make sure that 100 modules can be linked using jlink.
36+
* @bug 8240567
37+
* @library ../lib
38+
* @modules java.base/jdk.internal.jimage
39+
* jdk.jdeps/com.sun.tools.classfile
40+
* jdk.jlink/jdk.tools.jlink.internal
41+
* jdk.jlink/jdk.tools.jlink.plugin
42+
* jdk.jlink/jdk.tools.jmod
43+
* jdk.jlink/jdk.tools.jimage
44+
* jdk.compiler
45+
* @build tests.*
46+
* @run main/othervm -Xmx1g -Xlog:init=debug -XX:+UnlockDiagnosticVMOptions -XX:+BytecodeVerificationLocal JLink100Modules
47+
*/
48+
public class JLink100Modules {
49+
private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac")
50+
.orElseThrow(() -> new RuntimeException("javac tool not found"));
51+
52+
static void report(String command, String[] args) {
53+
System.out.println(command + " " + String.join(" ", Arrays.asList(args)));
54+
}
55+
56+
static void javac(String[] args) {
57+
report("javac", args);
58+
JAVAC_TOOL.run(System.out, System.err, args);
59+
}
60+
61+
public static void main(String[] args) throws Exception {
62+
Path src = Paths.get("bug8240567");
63+
64+
StringJoiner mainModuleInfoContent = new StringJoiner(";\n requires ", "module bug8240567x {\n requires ", ";\n}");
65+
66+
for (int i = 0; i < 1_000; i++) {
67+
String name = "module" + i + "x";
68+
Path moduleDir = Files.createDirectories(src.resolve(name));
69+
70+
StringBuilder moduleInfoContent = new StringBuilder("module ");
71+
moduleInfoContent.append(name).append(" {\n");
72+
if (i != 0) {
73+
moduleInfoContent.append(" requires module0x;\n");
74+
}
75+
moduleInfoContent.append("}\n");
76+
Files.writeString(moduleDir.resolve("module-info.java"), moduleInfoContent.toString());
77+
78+
mainModuleInfoContent.add(name);
79+
}
80+
81+
// create module reading the generated modules
82+
Path mainModulePath = src.resolve("bug8240567x");
83+
Files.createDirectories(mainModulePath);
84+
Path mainModuleInfo = mainModulePath.resolve("module-info.java");
85+
Files.writeString(mainModuleInfo, mainModuleInfoContent.toString());
86+
87+
Path mainClassDir = mainModulePath.resolve("testpackage");
88+
Files.createDirectories(mainClassDir);
89+
90+
Files.writeString(mainClassDir.resolve("JLink100ModulesTest.java"), """
91+
package testpackage;
92+
93+
public class JLink100ModulesTest {
94+
public static void main(String[] args) throws Exception {
95+
System.out.println("JLink100ModulesTest started.");
96+
}
97+
}
98+
""");
99+
100+
String out = src.resolve("out").toString();
101+
javac(new String[]{
102+
"-d", out,
103+
"--module-source-path", src.toString(),
104+
"--module", "bug8240567x"
105+
});
106+
107+
JImageGenerator.getJLinkTask()
108+
.modulePath(out)
109+
.output(src.resolve("out-jlink"))
110+
.addMods("bug8240567x")
111+
.call()
112+
.assertSuccess();
113+
114+
Path binDir = src.resolve("out-jlink").resolve("bin").toAbsolutePath();
115+
Path bin = binDir.resolve("java");
116+
117+
ProcessBuilder processBuilder = new ProcessBuilder(bin.toString(),
118+
"-XX:+UnlockDiagnosticVMOptions",
119+
"-XX:+BytecodeVerificationLocal",
120+
"-m", "bug8240567x/testpackage.JLink100ModulesTest");
121+
processBuilder.inheritIO();
122+
processBuilder.directory(binDir.toFile());
123+
Process process = processBuilder.start();
124+
int exitCode = process.waitFor();
125+
if (exitCode != 0)
126+
throw new AssertionError("JLink100ModulesTest failed to launch");
127+
}
128+
}

0 commit comments

Comments
 (0)
Please sign in to comment.