Skip to content
This repository has been archived by the owner on Oct 21, 2023. It is now read-only.

Commit

Permalink
8240567: MethodTooLargeException thrown while creating a jlink image
Browse files Browse the repository at this point in the history
Co-authored-by: Christoph Schwentker <siedlerkiller@gmail.com>
  • Loading branch information
koppor and Siedlerchr committed Jun 11, 2023
1 parent 16c3d53 commit 7c48693
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<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 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<Set>, 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, "<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();
});

// 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<ModuleInfo> 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, "<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(); // 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_();
});
}
}

/**
Expand Down
125 changes: 125 additions & 0 deletions test/jdk/tools/jlink/JLink100Modules.java
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 7c48693

Please sign in to comment.