Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2068,5 +2068,29 @@ public SignatureSet nextSignature() {
return null;
}

public void populateMethodUsageIndex(Map<String, List<BytecodeMethod>> index) {
if(isEliminated()) return;

Set<String> uniqueCalls = new HashSet<String>();

for(Instruction ins : instructions) {
String sname = ins.getMethodName();
if(sname != null) {
String sig = ins.getSignature();
if (sig != null) {
String key = sname + sig;
if (!uniqueCalls.contains(key)) {
uniqueCalls.add(key);
List<BytecodeMethod> callers = index.get(key);
if (callers == null) {
callers = new ArrayList<BytecodeMethod>();
index.put(key, callers);
}
callers.add(this);
}
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ public class Parser extends ClassVisitor {
private String clsName;
private static String[] nativeSources;
private static List<ByteCodeClass> classes = new ArrayList<ByteCodeClass>();
private static Map<String, List<BytecodeMethod>> usageIndex;
private int lambdaCounter;
public static void cleanup() {
nativeSources = null;
classes.clear();
LabelInstruction.cleanup();
usageIndex = null;
}
public static void parse(File sourceFile) throws Exception {
if(ByteCodeTranslator.verbose) {
Expand Down Expand Up @@ -434,7 +436,9 @@ public static void writeOutput(File outputDirectory) throws Exception {
if (BytecodeMethod.optimizerOn) {
System.out.println("Optimizer On: Removing unused methods and classes...");
Date now = new Date();
buildMethodUsageIndex();
neliminated += eliminateUnusedMethods();
usageIndex = null;
Date later = new Date();
long dif = later.getTime()-now.getTime();
System.out.println("unusued Method cull removed "+neliminated+" methods in "+(dif/1000)+" seconds");
Expand Down Expand Up @@ -488,6 +492,15 @@ public boolean accept(File file) {

}

private static void buildMethodUsageIndex() {
usageIndex = new HashMap<String, List<BytecodeMethod>>();
for(ByteCodeClass bc : classes) {
for(BytecodeMethod m : bc.getMethods()) {
m.populateMethodUsageIndex(usageIndex);
}
}
}

private static int eliminateUnusedMethods() {
return(eliminateUnusedMethods(false, 0));
}
Expand Down Expand Up @@ -613,6 +626,24 @@ private static boolean isMethodUsed(BytecodeMethod m, ByteCodeClass cls) {
if (!m.isEliminated() && m.isMethodUsedByNative(nativeSources, cls)) {
return true;
}

if (usageIndex != null) {
String name = m.getMethodName();
if ("__INIT__".equals(name)) {
name = "<init>";
}
String key = name + m.getSignature();
List<BytecodeMethod> callers = usageIndex.get(key);
if (callers != null) {
for (BytecodeMethod caller : callers) {
if (!caller.isEliminated() && caller != m) {
return true;
}
}
}
return false;
}

for(ByteCodeClass bc : classes) {
for(BytecodeMethod mtd : bc.getMethods()) {
if(mtd.isEliminated() || mtd == m) {
Expand Down
142 changes: 142 additions & 0 deletions vm/tests/src/test/java/com/codename1/tools/translator/StressTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.codename1.tools.translator;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class StressTest {

private Path tempDir;
private String originalOptimizer;

@BeforeEach
public void setup() throws Exception {
Parser.cleanup();
tempDir = Files.createTempDirectory("stress-test");
originalOptimizer = System.getProperty("optimizer");
System.setProperty("optimizer", "on");
// Force re-initialization of optimizerOn flag in BytecodeMethod if needed,
// but it's static final initialized at load time.
// Actually BytecodeMethod.optimizerOn is static but not final, but it is initialized in static block.
// We might need to reflectively set it if the class is already loaded.
// However, tests run in separate forks or BytecodeMethod might be reloaded?
// Let's assume we might need to toggle it via reflection if it's already loaded.

try {
java.lang.reflect.Field f = BytecodeMethod.class.getDeclaredField("optimizerOn");
f.setAccessible(true);
f.set(null, true);
} catch (Exception e) {
e.printStackTrace();
}
}

@AfterEach
public void teardown() throws Exception {
Parser.cleanup();
deleteDir(tempDir.toFile());
if (originalOptimizer != null) {
System.setProperty("optimizer", originalOptimizer);
} else {
System.clearProperty("optimizer");
}
}

private void deleteDir(File file) {
if (file.isDirectory()) {
for (File f : file.listFiles()) {
deleteDir(f);
}
}
file.delete();
}

@Test
public void testChainOfMethods() throws Exception {
// Creates a long chain of dependencies: Class0.main -> Class0.m0 -> Class0.m1 ... -> Class0.mN -> Class1.m0 ...
int numClasses = 50;
int methodsPerClass = 50;

System.out.println("Generating " + numClasses + " classes with " + methodsPerClass + " methods each...");
for (int i = 0; i < numClasses; i++) {
createClass(i, numClasses, methodsPerClass);
}

System.out.println("Parsing classes...");
File[] files = tempDir.toFile().listFiles((d, n) -> n.endsWith(".class"));
for (File f : files) {
Parser.parse(f);
}

System.out.println("Running optimizer...");
long start = System.currentTimeMillis();
File outputDir = Files.createTempDirectory("stress-output").toFile();
try {
// Mock ByteCodeTranslator.output to C or something valid
ByteCodeTranslator.output = ByteCodeTranslator.OutputType.OUTPUT_TYPE_IOS;

Parser.writeOutput(outputDir);
} finally {
deleteDir(outputDir);
}
long end = System.currentTimeMillis();
System.out.println("Optimization and write took: " + (end - start) + "ms for " + (numClasses * methodsPerClass) + " methods.");
}

private void createClass(int index, int totalClasses, int methodsPerClass) throws Exception {
String className = "Class" + index;
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);

// Add constructor
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();

// Add main method if index 0
if (index == 0) {
mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
// Call method 0 of class 0
mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, "method0", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}

// Add methods
for (int m = 0; m < methodsPerClass; m++) {
mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "method" + m, "()V", null, null);
mv.visitCode();

// Call next method or method in next class to create dependency chain
if (m < methodsPerClass - 1) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, "method" + (m + 1), "()V", false);
} else if (index < totalClasses - 1) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "Class" + (index + 1), "method0", "()V", false);
}

mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
}

cw.visitEnd();

FileOutputStream fos = new FileOutputStream(new File(tempDir.toFile(), className + ".class"));
fos.write(cw.toByteArray());
fos.close();
}
}
Loading